Tuesday, December 23, 2014

Customizing Samba on an ASUSWRT wireless router

Out of the box, the Asus RT-AC87 router has some handy, but limited, file and media sharing capabilities. Connect a USB hard drive to one of its USB ports, and the router can share data from that drive with anyone on your network - or optionally, with the outside world. The firmware implements Samba (a Linux-based program for sharing files similar to Windows file shares), but through the web interface you have only two options: allow everyone complete and anonymous access, or require a username and password for every connection. Samba can be configured far more granularly, but you cannot get there from the RT-AC87 web interface.

I store all my music and movie files on a file share, so music and movies can be played by any devices on my home network. A couple of years ago I used a NAS - a Network Attached Storage device (a hard drive that connects directly to the network and that has its own operating system), and I wrote about hacking it to create a genuinely useful music and movie server. Alas the hard drive crashed as all hard drives eventually will. My original thought was to replace it with something similar, but I couldn't find anything reasonably priced on the shelf and was too impatient to wait for an online order to ship. $600 for a 4 terabyte NAS versus $109 for a 4 terabyte USB hard drive? The choice for me was pretty easy. Sure the $600 drive had fault-tolerance features to ensure a future hardware failure didn't wipe out my data, but I have other means for backing up my most vital data ... I couldn't see spending over 5x the money for that.

Having bought the hard drive, I now needed a way to make the music and movies on it available on my home network. That meant deciding what computer to attach the drive to - a USB hard drive does not itself have a network port, so it has to plug into something. Many modern home / small office routers have USB ports for just that purpose, and my Asus RT-AC87 is no exception. In fact, this router has built-in apps for several classes of devices one might want to attach, including network storage, printers, and even a 3G/4G modem for cellular data (ever go somewhere that has no Internet? You can plug your cellphone into the router and eat up your data plan in a hurry!). There is even the option to make files available through the Internet and not just on your local network, though I'd advise extreme caution before doing that.

ASUSWRT includes a number of built-in applications for handling USB devices such as storage drives and 3G/4G modems

As I mentioned earlier though, the web GUI for setting up network storage has only two options: allow guest login (in which case anyone with access to the local network has full read and write access to the attached hard drive, with no restrictions and no logging of whodunit), or don't allow guest login (in which case you must set up user accounts and grant or deny each account appropriate permissions to each shared folder, and each person on your home network must log in to gain access to the shared files).
The GUI controls for configuring Samba are simple but limited

There's a middle ground between allowing anonymous guests full access, and requiring a login for every access. Why would I want a middle ground? I need read/write access to the drive so I can add and remove media files, and change playlists. The rest of my family accesses the same drive from their tablets, smartphones, PCs, and even the network-enabled Blu-Ray player. I don't want the kids to accidentally delete music and playlists that I have so carefully put together (yes, I learned this the hard way ... showing a 10-year-old how to use an Android SMB Client app to download my music onto her device without teaching her the difference between her files and my files was a bad idea). I could give them an account to log in with, but that's a bit inconvenient for my purposes. Since this is not sensitive information I prefer to allow anonymous guests read-only access from within my network.

Underneath the hood, the RT-AC87 is running a standard Samba daemon, in this case version 3.0.25b.2. Samba is a very powerful, very configurable service, configurable by modifying the /etc/smb.conf file. One of the configurable options is precisely what I need to accomplish anonymous read-only access for everyone plus authenticated read/write access for me:

The smb.conf setting for "map to guest" allows us to modify handling of unknown or anonymous users

The key is the configuration option map to guest = Bad User. This tells Samba, if it does not recognize the user requesting access, consider that user to be the guest, and log in using the guest account. If someone on my network browses to \\<my router ip>\<my share name> from Windows, Windows will try to log in as whomever is logged into the Windows computer first, and as an anonymous user second. So long as the Windows user does NOT match a user set up on the router, Samba will not recognize the user and instead see him or her as bad user, and treat it as the guest account.

With this, I can open the file share as myself (with the ability to add and delete media files, and to change playlists), or as an anonymous guest (with only the ability to open and play media files, but not delete or change them). Perfect for a home media server used by a houseful of teenagers!

Creating a custom smb.conf file lets me customize other aspects of the file sharing service as well. For instance, Asus sets up Samba to share every folder on the root of the USB drive as a separate file share. I don't necessarily want that; by customizing the configuration file, I can remove sections for folders that do not need to be shared on the network. Alternately, I can use the browseable=no option for an individual share to keep it enabled, but not shown in a list of available shares.

Changing the configuration file is great - but there's a slight conundrum here: the configuration file is stored in flash memory, and is regenerated from scratch when the router reboots or loses power. I can telnet into the router, replace the configuration file, and restart smbd (the Samba service) manually, but that's rather inconvenient. Not only that, but it means the shares come up in a state I do not want them in if there is a power outage when I am away from home. Fortunately, the ASUSWRT firmware design gives a very handy option.

ASUSWRT is the powerful, and increasingly customizeable, "unified firmware" Asus uses for their wireless routers. It's a common codebase used across many different models, such that many features and settings look and feel the same regardless of which Asus model you choose. Even better, source code for the entire thing is freely available, meaning if you are so inclined you can pore over source code to better understand how individual features work. Blogger Darell Tan put together a handy introductory primer to ASUSWRT at irq5.io, and developer Merlin (aka Eric Sauvageau) builds customized ASUSWRT-Merlin versions to add features or fix bugs in the official code.

One undocumented feature in ASUSWRT is the ability to specify a custom script to execute anytime a USB drive is mounted. Since a USB drive that is plugged into the router has to be mounted before use, this feature also has the effect of substituting for a "run at boot" script. ASUSWRT also provides a small portion of persistent storage useful for placing files that you don't want to be deleted upon rebooting. This folder is located at /jffs, and is a convenient place to store custom scripts. Just be aware that while /jffs is not disturbed by rebooting, it might be deleted upon installing an updated firmware. I keep a backup copy of my custom scripts elsewhere, but for simplicity sake I run them from /jffs.

The syntax to cause a script to run upon mounting a USB drive is:


nvram set script_usbmount="/jffs/scriptname"
nvram commit


Make sure the file permissions allow executing (running) the script; since this is Unix, the simplest way to do this is chmod 755 scriptname. Incidentally, there is also a script_usbumount option that runs upon unmounting or removing a USB drive. If you are so inclined, run nvram show to see a very long list of parameters ASUSWRT stores.

Unfortunately, during a reboot the USB drive is mounted before the Samba service starts. When ASUSWRT starts the Samba service, it rebuilds the smb.conf file from its defaults. That means we have a little bit of a sequencing problem: script_usbmount runs, then the smb.conf file is rebuilt, wiping away our change. There's a way around this too though.

nvram has another parameter success_start_service that is set after all services have started. By monitoring that parameter, we can cause the custom script to wait for the system to finish loading before it copies our smb.conf file into place. Thanks to irq5.io for this tip:


i=0
while [ $i -le 20 ]; do
   success_start_service=`nvram get success_start_service`
   if [ "$success_start_service" == "1" ]; then
       break
   fi
   i=$(($i+1))
   echo "autorun APP: wait $i seconds...";
   sleep 1

done


With that done, the script can now copy the customized smb.conf file into place. All that remains is to stop and restart the already-running smbd service. Alas, if we stop and restart it using the normal service controls, it will merely rebuild the default configuration file, defeating our purpose. My solution is to search for and kill any running smbd processes , then copy the smbd.conf file into place and restart smbd:


for pid in `ps -w | grep smbd | grep -v grep | awk '{print $1}'`
do
   echo "killing $pid"
   kill $pid
done
cp /jffs/smb.conf /etc/
smbd -D -s /etc/smb.conf


The code above may look Greek if you are not familiar with Unix commands. pid is the process ID, a unique number that identifies a running process. ps is the Unix command for "show me all running processes," similar to the Windows Task Manager; ps -w just says "give me more details." grep is an amazingly useful search function. awk is a string manipulation tool, handy for extracting a part of a string; in this case, the process list includes a number of things I don't need, so I use awk to grab just the process ID.

The complete script, with instructions as well as a sample smb.conf configuration file, are available on github: https://github.com/dnlongen/ASUSWRT_Samba_Fixer

Update January 8: Researcher Joshua Drake found a bug in the infosvr service that runs on ASUSWRT, and can be used by anyone on the local network to run any command they wish on the router. There is no patch yet available, but the technique I used for fix Samba can also be used to shut off the vulnerable service and protect your router. More details available in a separate post.