Since my D-Link router kicked the bucket some days ago I had to buy a new router, I heard good stuff about Fritz products and decided to go with a Frittz!Box 3273 which is basicly their low-end product.
One thing I totally did not expect was the support for running the router as a UPnP server. It can either serve data from devices attached to two local USB ports or from external WebDAV resources. This gives me the chance to power down my current Servioo based server and save some power.
Unfortunately, there are known issues around the media server having to do with not refreshing properly if the contents of a mounted USB medium change. I digged arounnd some internet posts but did not come to a good solution and peeked into it myself.
Here's what I found out. The solution is not optimal and hopefully obsolete when AVM puts out some update in the future. Apart from the solution itself I found out some interesting things, you might be interested in as well.
Manually Forcing a Refresh
The router maintains a database of files present on a storage medium which is called fritznasdb_part.db3 and saved in the medium itself. As the post linked above noted, this is a SqlLite database and you can peek into it with tools like SQLLiteManager. Some people suggested that just deleting this file should do the job, however when I tried it did not get recreated afterwards, however it did get created, when I navigated to the web-based file browser.
So hat I take away from this is, if I find a ways to delete the file and then automatically call the file browser, I would have a way to refresh the media server repository without the use of manual steps.
Automating the Solution
Automating the thing turned out to be a little more challenging than I originally expected. This has to do with the way logging in works. I took a trace with Fiddler to see what get's transmitted to the server when I enter my password and hit the login button.
What we see here is the first request being the POST, that submits the password and the second the home screen being loaded. The 303 code (see other) returned from the POST performs a redirect which specifies to load the home screen afterwards.
HTTP/1.1 303 See Other
Keep-Alive: timeout=60, max=300
Apart from the 303 code instead of 301 (object moved), this is pretty straight forward forms authentication. Perhaps there is one speciality - most of the times you see a session token returned in the response as a cookie, here they use a querry string parameter called sid to keep the session state.
Peeking into the request, I expected the password to be submitted with a form post, however this was not the case:
POST http://192.168.188.1/login.lua HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20100101 Firefox/31.0
Accept-Encoding: gzip, deflate
A nice way to do that with chrome is by selecting the form element on the page and having a look at the event listeners panel.
Clicking on the red marked hyperlink will directly take you to the code that runs when logging in.
var dot_pass = makeDots(jxl.getValue("uiPass"))
var resp = g_challenge + "-" + dot_pass;
jxl.setValue("uiResp", g_challenge + "-" + hex_md5(resp));
The makeDots function is quite straightforward - it replaced all special characters in the password with dots. g_Challenge contains a hex string (e.g. 32745db9), which is then concatenated with the password (e.g. 32745db9-password). After that the method hex_md5 is used to process this string, it's output is concatenated to the challenge again and the result is written to the hidden form field uiResp (which is the response field we saw in the fiddler trace).
var challenge = "32745db9";
StringBuilder script = new StringBuilder();
context.SetParameter("rawText", challenge + "-" + password);
scrambledText = (string)context.GetParameter("rawText");
As you see, i load up the md5.js library and a custom one called main.js which are both embedded resources in the application. I set the value of the parameter rawText by concatenating challenge and password, just the way the Fritz!Box does it. After I run the script I extract the value from the parameter. Now let's take a look into main.js:
rawText = hex_md5(rawText);
Well - that was easy :P Ok, with this in place we are able to perform the password-scrambling ourselves. What we have to do now is to get the challenge. For this I make a request to the login page using HTTP get and extract the g_Challenge value via a regular expression:
var webClient = new WebClient();
var content = webClient.DownloadString("http://" + address + "/logincheck.lua");
var challengeRegex = new Regex(@"g_challenge = ""(.+)""");
var contentMatch = challengeRegex.Match(content);
var challenge = contentMatch.Groups.Value;
With the challenge being parsed and hex_md5 having generated the hash, we can push out the HTTP post to get to our session token.
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://" + address + "/login.lua");
request.AllowAutoRedirect = false;
request.Method = "post";
request.ContentType = "application/x-www-form-urlencoded";
using (var sw = new StreamWriter(request.GetRequestStream()))
sw.Write("response=" + challenge + "-" + scrambledText + "&page=%2Fhome%2Fhome.lua&username=&site_mode=classic");
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
var sessionToken = new Uri(response.Headers["Location"]).Query.Substring("?sid=".Length);
Note the we need to disable AllowAutoRedirect, because otherwise the web client would follow the redirect and we could not look at the response for our HTTP post but only at the response of the hone page. The session token is extracted in the last line by looking at the sid query parameter. Ok, at this point we have a valid session and can perform the navigation to the file browser inside of it. To find out the url we're interested in, I did another Fiddler trace.
You see, the request contains the sid again. This is the one we got in the prior step. In addition there is an parameter called tXXXX, which I assume to be a random token to prevent caching. I used random values here and did not get problems.
So here is the last step that causes the box to actually refresh:
var randomToken = Environment.TickCount.ToString();
var result = webClient.DownloadString("http://" + address + "/nas/ajax_files_browse.lua?sid=" + sessionToken + "&dir=%2f&site=files&site_mode=classic&browse_mode=type:directory&start_entry=1&total_file_count=0&xhr=1&t1" + randomToken + "=nocache");
After running this request, the fritznasdb_part.db3 file is recreated. One small nuisance I cannot resolve is, that when using my smart TV, i have to navigate to the folder twice to see the data - but hey - better than nothing.
I hope you found this post interesting. I attached the code to it, so you can play with it yourself if you like.
fritz-refresh.zip.zip (6,16 mb)