Server:Server Status

Improving the Motorola Blink Baby Monitor/Camera (Part…6?!?)

After all this time...why am I still working on this? The cameras are dead, Motorola shut down the monitoreverywhere service, so the cameras are dead! You can't register them, or join to cameras remotely, right?

Well, they still work on the LAN, so if they're already on the wifi, you can use them as little RTSP streams. The problem is...I want to change my SSID's, and there's no way to rejoin to wifi without the app connecting with the (now offline) monitoreverywhere service.

Enter crazy old Joe. I had a Titanium Backup of the apk and my config from an android device I don't even have anymore. I was able to extract this and decompile it to learn how the app handles registration, then emulate that. First, from previous research, when you factory reset a camera it broadcasts a "Camera-######" SSID when you can connect to as it's open. You'll get a 192.168.2.# address, and the camera will be "192.168.2.1". From there, you can start performing operations on the camera, for example, If you go here in a web browser: http://192.168.2.1/?action=command&command=enable_telnet You will then be able to telnet to the camera.

From previous work, we know a lot of the scripts and commands on there, but I never found how to format the wifi request. So, I started poking through the apk. Here's some snippets that are useful:

this.cam_profile.setBasicAuth_usr("camera");
this.cam_profile.setBasicAuth_pass("000000");

The camera has a basic http authentication piece, and that is the default username and password...maybe. I also found this in defining the variables:

this.usr_name = usrName == null ? "" : usrName;
this.pass_wd = pwd == null ? "" : pwd;

So the user and password will either be "camera:000000" or ":". We can try both.

public String build_setup_core_request() {
    String auth_mode;
    String key_index;
    if (this.security_type.equalsIgnoreCase("WEP")) {
        auth_mode = this.auth_method.equalsIgnoreCase("Open") ? "0" : "1";
        key_index = String.format("%d", Integer.valueOf(Integer.parseInt(this.key_index) - 1));
    } else if (this.security_type.equalsIgnoreCase("OPEN")) {
        auth_mode = "0";
        key_index = "0";
    } else {
        auth_mode = "2";
        key_index = "0";
    }
    String ssid_len = String.format("%03d", Integer.valueOf(this.ssid.getBytes().length));
    String sec_key_len = String.format("%02d", Integer.valueOf(this.pass_string.length()));
    String usr_name_len = String.format("%02d", Integer.valueOf(this.usr_name.length()));
    String passwd_len = String.format("%02d", Integer.valueOf(this.pass_wd.length()));
    String setup_value = String.valueOf("1") + "00" + auth_mode + key_index + "0" + ssid_+ sec_key_len + "0000000" + usr_name_len + passwd_len + this.ssid + this.pass_string + .usr_name + this.pass_wd;
    if (shouldEncodeData()) {
        try {
            setup_value = URLEncoder.encode(setup_value, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        Log.d(GcmIntentService.TAG, "Encode setup data");
    } else {
        Log.d(GcmIntentService.TAG, "No need to encode setup data.");
    }
    String setup_request = "/?action=command&command=setup_wireless_save&setup=" + setup_value;
    return setup_request;
}

That is exactly what we need. First of all, since the security type would be "WPA2/PSK" auth_mode is 2 and key_index is 0. The SSID length is formatted to be 3 digits (mine is 32 characters, so 032), and the passcode length is formatted to be 2 digits (mine is 26 characters, so 26).

That means the pieces needed break down into one of these 2:

1 00 2 0 0 032 26 000000 06 06 SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS PPPPPPPPPPPPPPPPPPPPPPPPPP camera 000000

or

1 00 2 0 0 032 26 000000 00 00 SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS PPPPPPPPPPPPPPPPPPPPPPPPPP

Following all that string concatenation, we have 2 options to set up the wifi, depending on which password configuration it needs (SSID and passcode are obfuscated):

http://192.168.2.1/?action=command&command=setup_wireless_save&setup=1002000322600000000606SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSPPPPPPPPPPPPPPPPPPPPPPPPPPcamera000000
http://192.168.2.1/?action=command&command=setup_wireless_save&setup=1002000322600000000000SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSPPPPPPPPPPPPPPPPPPPPPPPPPP

The 2nd one worked; the camera made a loud beep but the SSID was still active. I telnetted to the camera and this was in /var/log/messages:

Jan  1 15:54:57 MJPG-streamer [1901]: Org client thread : GET /?action=command&command=setup_wireless_save&setup=1002000322600000000000SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSPPPPPPPPPPPPPPPPPPPPPPPPPP HTTP/1.1^M
Jan  1 15:54:57 MJPG-streamer [1901]: After URL Encode : GET /?action=command&command=setup_wireless_save&setup=1002000322600000000000SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSPPPPPPPPPPPPPPPPPPPPPPPPPP HTTP/1.1^M
Jan  1 15:54:57 MJPG-streamer [1901]: Connect 192.168.2.11
Jan  1 15:54:57 MJPG-streamer [1901]: access granted
Jan  1 15:54:57 MJPG-streamer [1901]: command string: setup_wireless_save
Jan  1 15:54:57 MJPG-streamer [1901]: setup=1002000322600000000000SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSPPPPPPPPPPPPPPPPPPPPPPPPPP HTTP/1.1^M
Jan  1 15:54:57 MJPG-streamer [1901]: len=91
Jan  1 15:54:57 MJPG-streamer [1901]: 1002000322600000000000SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSPPPPPPPPPPPPPPPPPPPPPPPPPP HTTP/1.1^M
Jan  1 15:54:57 MJPG-streamer [1901]: len SSID 32 and index 9
Jan  1 15:54:57 MJPG-streamer [1901]: Key Length = 26
Jan  1 15:54:57 MJPG-streamer [1901]: String got is:0
Jan  1 15:54:57 MJPG-streamer [1901]: Len of workport is:0
Jan  1 15:54:57 MJPG-streamer [1901]: SSID='SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS' - Key='PPPPPPPPPPPPPPPPPPPPPPPPPP'
Jan  1 15:54:57 MJPG-streamer [1901]: Before sem wait
Jan  1 15:54:57 MJPG-streamer [1901]: After sem wait
Jan  1 15:54:57 MJPG-streamer [1901]: Finish init Flash
Jan  1 15:54:57 MJPG-streamer [1901]: Flash Init 20 (1901)
Jan  1 15:54:57 MJPG-streamer [1901]: Before sem post
Jan  1 15:54:57 MJPG-streamer [1901]: Flash DeInit 20 (1901)
Jan  1 15:54:57 MJPG-streamer [1901]: Finish deinit Flash
Jan  1 15:54:57 MJPG-streamer [1901]: After sem post
Jan  1 15:54:57 MJPG-streamer [1901]: ===WRITE TO FLASH===
Jan  1 15:54:57 MJPG-streamer [1901]: Before sem wait
Jan  1 15:54:57 MJPG-streamer [1901]: After sem wait
Jan  1 15:54:57 MJPG-streamer [1901]: Finish init Flash
Jan  1 15:54:57 MJPG-streamer [1901]: Flash Init 20 (1901)
Jan  1 15:54:57 MJPG-streamer [1901]: Before sem post
Jan  1 15:54:57 MJPG-streamer [1901]: Flash DeInit 20 (1901)
Jan  1 15:54:57 MJPG-streamer [1901]: Finish deinit Flash
Jan  1 15:54:57 MJPG-streamer [1901]: After sem post
Jan  1 15:54:57 MJPG-streamer [1901]: Wifi configuration saved successfully
Jan  1 15:54:57 MJPG-streamer [1901]: leaving

It looked like it took, so I rebooted the camera, and it connected and got its proper assigned DHCP address! I am able to view the camera connected to the correct SSID! As far as I'm concerned, the camera is now back to as operational as I want it to be, despite the vendor's abandoning of the device.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

My experience/guide setting up OpenLDAP for PC/webapp authentication on Ubuntu 20.04.

This was originally posted Here but I'm cross-posting it to my site for preservation and self-reliance.

I've been experimenting with user and account management solutions for my home lab, and I was looking for something that could manage posix users and web users. I wanted something with group management and OU hierarchies that sort of follow what you would use in Active Directory. I found a lot of issues with some of the more well recommended solutions out there:

  • Samba: It's basically an AD Domain Controller which was more than I needed; also while being feature-familiar with AD is nice, I'm not looking to implement AD, I'm looking to really stick closer to just LDAP.
  • GLAuth: Interesting project, but is somewhat limited in the attributes it can handle, how group management works, how the OU structure is reflected in group membership, and usability. GLauth-ui is an interesting component for usability in that it allows for user password resets and such, however it's not mature enough yet for what I need.
  • FreeIPA: It isn't an option on Ubuntu Servers anymore which is my target platform; since I believe 18.04 there are dependency resolution issues and the issue trackers indicate there is no path to resolution.
  • Æ-DIR: This seemed interesting, but at the time I looked into it there was a deal breaker which I can no longer remember; I think it didn't do both posix auth and web-app integration well enough, but it did one or the other well? I'm not sure, it's been a while.

Part of all this was wanting to learn more about how LDAP worked in general, so I had a driver in the back of my mind to stick to pure openLDAP. While that seemed very intimidating at first, it's become the solution at the top of my list. That being said, I don't think it's great out-of-the-box for the average homelab setup. The default objectClasses inherit from top and have attributes that overlap in undesireable ways, so a user created in one of these classes for posix authentication may not work well for, say, a webapp whose privileges are assigned by OU or group. To this end, I was driven to create a custom schema with objectClasses that are more reflective of what I believe someone would want in a homelab. There's a lot of counter-intuitive stuff here, which I want to document for anyone else going down this path. This link has been a tremendous resource: https://www.openldap.org/doc/admin22/schema.html

Custom LDAP schemas should be globally unique, and that is handled through an OID. To avoid OID conflict, you should go here and request your own OID: https://pen.iana.org/pen/PenApplication.page The turnaround is like a week. With this, I could create objectClasses like "surfrock66User" just for me, or if someone wanted to be more enterprising and create a worldwide-useful objectClass like "homeLabUser" they could get an OID to hold. For my purposes, since I'm experimenting with changing schema, it makes sense to have my own id space to play in. There is also room for using 1.1 based OID's as kind of unreserved space, kind of like 192.168.1.* for IPs. When you get your final OID, it'll be of the format "1.3.6.1.4.1._____." and you append your structure after that.

I need 2 custom objectClasses (Users and Groups), but will be experimenting with a third as well later (Devices); I separate the third as I don't really have a use case, so I don't feel confident in an attribute list that would represent a custom class yet. One thing that is an issue, I expect group membership to be reflected both in the group and the user; that's not part of OpenLDAP to start, you need to install the memberOf overlay. Here's a good guide: https://kifarunix.com/how-to-create-openldap-member-groups/

Below are the attributes in the default relevant objectClasses:

objectclass ( 2.5.6.6 NAME 'person'
    DESC 'RFC2256: a person'
    SUP top STRUCTURAL
    MUST ( sn $ cn )
    MAY ( userPassword $ telephoneNumber $ seeAlso $ description ) )
objectclass ( 2.5.6.7 NAME 'organizationalPerson'
    DESC 'RFC2256: an organizational person'
    SUP person STRUCTURAL
    MAY ( title $ x121Address $ registeredAddress $ destinationIndicator $
        preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $
        telephoneNumber $ internationaliSDNNumber $ 
        facsimileTelephoneNumber $ street $ postOfficeBox $ postalCode $
        postalAddress $ physicalDeliveryOfficeName $ ou $ st $ l ) )
objectclass ( 2.5.6.10 NAME 'residentialPerson'
    DESC 'RFC2256: an residential person'
    SUP person STRUCTURAL
    MUST l
    MAY ( businessCategory $ x121Address $ registeredAddress $
        destinationIndicator $ preferredDeliveryMethod $ telexNumber $
        teletexTerminalIdentifier $ telephoneNumber $ internationaliSDNNumber $
        facsimileTelephoneNumber $ preferredDeliveryMethod $ street $
        postOfficeBox $ postalCode $ postalAddress $
        physicalDeliveryOfficeName $ st $ l ) )
objectclass ( 1.3.6.1.1.1.2.0 NAME 'posixAccount'
    DESC 'Abstraction of an account with POSIX attributes'
    SUP top AUXILIARY
    MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory )
    MAY ( userPassword $ loginShell $ gecos $ description ) )
objectclass ( 1.3.6.1.1.1.2.1 NAME 'shadowAccount'
    DESC 'Additional attributes for shadow passwords'
    SUP top AUXILIARY
    MUST uid
    MAY ( userPassword $ shadowLastChange $ shadowMin $
        shadowMax $ shadowWarning $ shadowInactive $
        shadowExpire $ shadowFlag $ description ) )
objectclass     ( 2.16.840.1.113730.3.2.2
    NAME 'inetOrgPerson'
    DESC 'RFC2798: Internet Organizational Person'
    SUP organizationalPerson
    STRUCTURAL
        MAY (
        audio $ businessCategory $ carLicense $ departmentNumber $
        displayName $ employeeNumber $ employeeType $ givenName $
        homePhone $ homePostalAddress $ initials $ jpegPhoto $
        labeledURI $ mail $ manager $ mobile $ o $ pager $
        photo $ roomNumber $ secretary $ uid $ userCertificate $
        x500uniqueIdentifier $ preferredLanguage $
        userSMIMECertificate $ userPKCS12 ) )

objectclass ( 2.5.6.9 NAME 'groupOfNames'
    DESC 'RFC2256: a group of names (DNs)'
    SUP top STRUCTURAL
    MUST ( member $ cn )
    MAY ( businessCategory $ seeAlso $ owner $ ou $ o $ description ) )
objectclass ( 1.3.6.1.1.1.2.2 NAME 'posixGroup'
    DESC 'Abstraction of a group of accounts'
    SUP top STRUCTURAL
    MUST ( cn $ gidNumber )
    MAY ( userPassword $ memberUid $ description ) )

Additionally, I wanted the sshPublicKey option from an optional schema http://pig.made-it.com/ldap-openssh.html

Out of this, I am proposing the following new schema for my lab...it has a lot of the other attributes in as MAY even though I have no plan to populate them, that being said if a day comes where I use something requiring an LDAP attribute I didn't build in I could see it not working:

objectclass ( 1.1.2.2.1 NAME 'domainAccount'
    DESC 'A user/account/person in the organization'
    SUP top STRUCTURAL
    MUST ( cn $ name $ sn $ uid )
    MAY ( 
        audio $ businessCategory $ carLicense $ departmentNumber $ 
        description $ destinationIndicator $ displayName $ 
        employeeNumber $ employeeType $ facsimileTelephoneNumber $ 
        gecos $ gidNumber $ givenName $ homeDirectory $ homePhone $ 
        homePostalAddress $ initials $ internationaliSDNNumber $ 
        jpegPhoto $ l $ labeledURI $ ldapPublicKey $ loginShell $ 
        mail $ manager $ mobile $ o $ ou $ pager $ photo $ 
        physicalDeliveryOfficeName $ postalAddress $ postalCode $ 
        postOfficeBox $ preferredDeliveryMethod $ 
        preferredLanguage $ registeredAddress $ roomNumber $ 
        secretary $ seeAlso $ shadowExpire $ shadowInactive $ 
        shadowLastChange $ shadowMax $ shadowMin $ shadowWarning $ 
        sshPublicKey $ st $ street $ telephoneNumber $ 
        teletexTerminalIdentifier $ telexNumber $ title $ 
        uidNumber $ userCertificate $ userPassword $ userPKCS12 $ 
        userSMIMECertificate $ x121Address $ x500uniqueIdentifier ) )

objectclass ( 1.1.2.2.2 NAME 'domainGroup'
    DESC 'A group of names in the organization'
    SUP top STRUCTURAL
    MUST ( cn $ member )
    MAY ( 
        businessCategory $ description $ gidNumber $ o $ ou $ 
        owner $ seeAlso ) )

Here's a more commented breakdown of the classes:

Per the documents, it's recommended to add this to your schema directory in its own .schema file, then include that in the slapd config AFTER the other includes (as attributes need to be previously defined). On ubuntu, you can install the schema2ldif package and it will help to install custom schema; you put the .schema file in the schema directory (/etc/ldap/schema/) and do "ldap-schema-manager -i /etc/ldap/schema/surfrock66Schema.schema".

To manage an LDAP environment, especially for things like user password resets, you'll want a simple web interface. Everyone will tell you to use LDAP Account Manager (LAM) which is awesome and more modern, however if you're using custom schema it will not support it unless you pay for LAM Pro which is like $260/year, and is not worth it in my opinion for a small homelab. To that end I fell back to phpLDAPAdmin which is kind of abandoned, but still works. The OS packages in the Ubuntu repositories won't work as they don't work with php7.2+, but there are installation instructions for the 1.2 branch on the github here: https://github.com/leenooks/phpLDAPadmin If you have an existing apache environment, it is very straightforward, just extract the zip file, create a site in apache, and edit the config.php. You'll do initial configuration with the admin user, using session bind. Once we get down the road, users can reset their passwords using phpLDAPAdmin, but it's not super easy...given the scope of my home lab this guide is good enough: https://www.linvirtshell.com/2017/11/openldap-user-password-reset-by-using.html

If I remove all the comments from my config.php file, this is the resultant configuration and it works well for me:

custom->appearance['hide_template_warning'] = true;
$config->custom->appearance['friendly_attrs'] = array(
        'facsimileTelephoneNumber' => 'Fax',
        'gid'                      => 'Group',
        'mail'                     => 'Email',
        'telephoneNumber'          => 'Telephone',
        'uid'                      => 'User Name',
        'userPassword'             => 'Password'
);
$servers = new Datastore();
$servers->newServer('ldap_pla');
$servers->setValue('server','name','Surfrock66\'s LDAP Server');
$servers->setValue('server','host','127.0.0.1');
$servers->setValue('server','port',389);
$servers->setValue('server','base',array('dc=example,dc=com'));
$servers->setValue('login','auth_type','session');
$servers->setValue('login','attr','dn');
$servers->setValue('login','base',array('ou=accounts,dc=example,dc=com'));
$servers->setValue('login','class',array('domainAccount'));
$servers->setValue('login','bind_dn_template','cn=%s,ou=accounts,dc=example,dc=com');
$servers->setValue('appearance','show_create',false);
$servers->setValue('appearance','open_tree',true);
$config->custom->session['reCAPTCHA-enable'] = false;
$config->custom->session['reCAPTCHA-key-site'] = '';
$config->custom->session['reCAPTCHA-key-server'] = '';
?>

Since we're using custom schema, we need to add templates for them so the UI can create my users. I'm not gonna dump the xml for that here, but it's well documented, you just create a copy of existing templates. http://phpldapadmin.sourceforge.net/wiki/index.php/Templates I will provide links to my templates below.

SO. TLDR; Let's say you just want to do everything I did. Here's some prototype commands that should get you started, with downloads of the files I made/collected:

sudo apt update
sudo apt -y install slapd ldap-utils schema2ldif 
# cd web root
sudo wget https://github.com/leenooks/phpLDAPadmin/archive/refs/tags/1.2.6.2.zip
sudo unzip 1.2.6.2.zip
sudo rm 1.2.6.2.zip
sudo mv phpLDAPadmin-1.2.6.2 phpLDAPadmin
sudo cp phpLDAPadmin/config/config.php.example phpLDAPadmin/config/config.php
sudo vi phpLDAPadmin/config/config.php
# Configure for your needs, ask google and there are lots of guides
sudo cd phpLDAPadmin/templates/creation
sudo wget https://www.surfrock66.com/ldap/custom_domainAccount.xml
# Edit the above file if you want to re-order or remove any attributes from the account creation form
sudo wget https://www.surfrock66.com/ldap/custom_domainGroup.xml
# Edit the above file if you want to re-order or remove any attributes from the group creation form
sudo cd ../../../
# if your apache is not running under www-data, change this command to that user
sudo chown -R www-data:www-data phpLDAPadmin
sudo cd /etc/ldap/schema
sudo wget https://www.surfrock66.com/ldap/2.schema.attribute.sshPublicKey.schema
sudo wget https://www.surfrock66.com/ldap/3.schema.objectClass.domainAccount.schema
# Edit the above file with your own OID if you requested one
sudo wget https://www.surfrock66.com/ldap/4.schema.objectClass.domainGroup.schema
# Edit the above file with your own OID if you requested one
mv 2.schema.attribute.sshPublicKey.schema ldapPublicKey.schema
mv 3.schema.objectClass.domainAccount.schema domainAccount.schema
mv 4.schema.objectClass.domainGroup.schema domainGroup.schema
cd ~/
wget https://www.surfrock66.com/ldap/1.config.InstallMemberOf.ldif
wget https://www.surfrock66.com/ldap/5.ous.ldif
# Edit this file with your actual domain name
sudo ldapadd -Y EXTERNAL -H ldapi:/// -f 1.config.InstallMemberOf.ldif
sudo ldapadd -Y EXTERNAL -H ldapi:/// -f 5.ous.ldif
sudo ldap-schema-manager -i /etc/ldap/schema/ldapPublicKey.schema
sudo ldap-schema-manager -i /etc/ldap/schema/domainAccount.schema
sudo ldap-schema-manager -i /etc/ldap/schema/domainGroup.schema
sudo systemctl restart slapd

At this point, you should be able to log into phpLDAPadmin using the admin account created in the slapd package config. You should make your groups before making your users; this could be changed if you moved the gidNumber from the "Must" to "May" of the domainAccount schema.

Once this is done, create 2 users using the GUI to test that things work; I made one for me, and one for ldap binds (so I can tattoo a minimally privileged account with an unimportant password to apps for binding) called ldapbinduser. The last step is to tattoo olcAccess rules to your configuration. My desired rules are the following:

  1. My user, for now, is like a Domain Admin that can manage everyting
  2. The ldapbinduser account will be used for binds, meaning it needs to authenticate and read the users and groups (basically the whole directory)
  3. Any user should be able to read their own attributes, authenticate, and change their own password
  4. Everything else is denied.

To that end, I made an ldif file called "accessRules.ldif" with the following contents, this article was instrucmental in making this: https://medium.com/@moep/keeping-your-sanity-while-designing-openldap-acls-9132068ed55c :

dn: olcDatabase={1}mdb,cn=config

changetype: modify
replace: olcAccess
olcAccess: {0}to *
  by dn="cn=admin,dc=example,dc=com" manage
  by dn="cn=surfrock66,ou=accounts,dc=example,dc=com" manage
  by dn="cn=ldapbinduser,ou=accounts,dc=example,dc=com" read
  by * break
-
add: olcAccess
olcAccess: {1}to dn.children="ou=accounts,dc=example,dc=com" attrs=userPassword,shadowExpire,shadowInactive,shadowLastChange,shadowMax,shadowMin,shadowWarning
  by self write
  by anonymous auth
-
add: olcAccess
olcAccess: {2}to dn.subtree="dc=example,dc=com"
  by self read

To get that into your configuration, use the following command:

ldapmodify -Y EXTERNAL -H ldapi:/// -f accessRules.ldif

With that, I have a working openLDAP setup. Applications and Linux computers are authenticating and pulling attributes. Based on what I've done here, it can be pretty easily adapted at a bunch of different points, but this is all the info I collected over the course of my research and should help to guide someone starting a similar journey.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Start to finish guide for creating a mumble server and hooking it into Bukkit/Spigot/Paper for interconnected chat, with a web interface.

This was originally posted to reddit Here but I am archiving the post here for posterity and self-reliance.

A long time ago we had IRC integrated into our server so offline people could chat, but with IRC becoming less used, we wanted to switch to something Discord-like which could enable voice chat. My preference is for self-hosted, open-source software where I control the data and service, so my target platform was Mumble. The result is pretty great; I have a easy mumble web client users can hang out in and talk with the server when they're offline, I have a phone app which receives notifications and does text to speech so with my headset I can be notified if a new user joins or if someone dies (this is all configurable). Users can choose to voice chat as well to collab on projects.

This wasn't as straightforward as I'd have thought, and ended up interconnecting MANY projects:

Mumble-Server/Murmur

Mumble-web-proxy

Mumble-web

Apache Web Server

Matterbridge

MatterBukkit

Deep in this guide, when we get to the part of this writeup about matterbridge, that's the meat of this. As much as I'm using this with Mumble, with that, you could integrate this with XMPP, Discord, Slack, Matrix...a bunch of things. That is our link between all the different forms of communication.

I am running this on the server that my Minecraft server is on which is Ubuntu 20.04, so everything internally is localhost, though some things want to be referred to by the fqdn. I will refer to my fqdn as mc.domain.com for this post. Additionally, I have actually paid for a verified SSL cert for this domain (I host a bunch of sites like dynmap as well) but this could be done with letsencrypt, either way you want SSL for this. I recommend paying the $20/year for a real ssl, but however you get the cert/key/chain is up to you.

This project required 4 ports, 3 of which will be public. I used non-standard ports listed below due to my hosting config (stuff from the same site uses a lot of default ports), but you can use whatever you want including the defaults:

Mumble: 50730

Mumble web proxy: 50731

Apache2 for mumble web: 50732

Internal port for MatterBridge: 4242

So, if a user is connecting with a proper mumble client, they connect to "mc.domain.com" port 50730, but if they just wanna use a web client, they connect with "https://mc.domain.com:50732". Since it's proper SSL, I can embed that in an iframe on my server's site which is nice, because I get a bit of ad revenue when people sit on there all day, we do the same thing with dynmap as well...they're unobtrusive banner ads from our hosting provider and not popups or overlays.

My ssl certs are located here:

  • /etc/apache2/ssl/mc.domain.com.pem
  • /etc/apache2/ssl/mc.domain.com.key
  • /etc/apache2/ssl/mc.domain.com.intermediate.crt

I will post my configs, with secrets obfuscated and comment lines removed which should be enough to get someone going.

First, I installed mumble-server per the guide listed above. The nice thing is it installs a user account, and I used that user account for most of the services we'll be setting up today (except for apache, which uses www-user by default). Once you've gone through the mumble-server configuration, this is what I have in /etc/mumble-server.ini:

database=/var/lib/mumble-server/mumble-server.sqlite
icesecretwrite=
logfile=/var/log/mumble-server/mumble-server.log
pidfile=/run/mumble-server/mumble-server.pid
welcometext="
Welcome to the DOMAIN Minecraft Mumble Server!
" port=50730 host= serverpassword= bandwidth=72000 users=100 messageburst=5 messagelimit=1 allowping=true defaultchannel=1 rememberchannel=false registerName=DOMAIN Minecraft Server sslCert=/etc/apache2/ssl/mc.domain.com.pem sslKey=/etc/apache2/ssl/mc.domain.com.key sslCA=/etc/apache2/ssl/mc.domain.com.intermediate.crt uname=mumble-server autobanAttempts=10 autobanTimeframe=120 autobanTime=300 [Ice] Ice.Warn.UnknownProperties=1 Ice.MessageSizeMax=65536

My mumble server has a "general chat" channel which is ID 1, and that's what I have set as the default...otherwise it'll be "root".

Once mumble is up and running, you should log in with the actual mumble client and do some config like make rooms, make your real account, get registered, make yourself an admin, etc. One thing to do is go to "Server->Access Tokens" and make an access token for later use, it's just a big secret key that is freetext but authorizes apps to connect.

The next thing is to get mumble web proxy working. This was one of the more complex parts because there's a bug where if you don't have ipv6 enabled it never falls back to ipv4, and to make that patch you have to use an older rustup to compile. It ended up being pretty simple, so I will reference my code patch, the build chain configuration needed, and the general build instructions. If it looks intimidating it isn't, once it compiles it makes a single binary executable. I made a folder "/etc/mumble-web-proxy" and put this binary there, then changed permissions on that file and folder to be owned by "mumble-server". I then made the following systemd unit file located in /etc/systemd/system/mumble-web-proxy.service :

[Unit]
Description=Mumble Web Proxy
After=network.target
After=mumble-server.service

[Service]
Type=simple
WorkingDirectory=/etc/mumble-web-proxy
PrivateUsers=true 
User=mumble-server
Group=mumble-server
ProtectSystem=full 
ProtectHome=true 
ProtectKernelTunables=true 
ProtectKernelModules=true 
ProtectControlGroups=true 

ExecStart=/etc/mumble-web-proxy/mumble-web-proxy --listen-ws 50731 --server mc.domain.com:50730 --ice-port-min 20000 --ice-port-max 21000

Restart=on-failure
RestartSec=60s

[Install]
WantedBy=multi-user.target

Pay attention to the ports, AND you need to refer to the server by the ssl name and NOT localhost/127.0.0.1. Once you do a "systemctl daemon-reload; systemctl enable mumble-web-proxy.service; systemctl start mumble-web-proxy.service" it should be running and visible with "netstat -tulpn".

Next is Apache, for mumble-web. There are millions of guides for installing apache on ubuntu so just find one, but in my case since I'm using a weird port, I had to edit /etc/apache2/ports.conf and make sure ssl is listening on my 50732 port. In the link above to mumble-web, follow the instructions to build this site with npm. I made a directory "/var/www/mumble" and put the contents of the resulting dist directory there. In that folder is a file called config.js, this file is a bit of a mess but your configuration goes at the bottom. Here is the relevant section of mine:

window.mumbleWebConfig = {
  // Which fields to show on the Connect to Server dialog
  'connectDialog': {
    'address': true,
    'port': true,
    'token': false,
    'username': true,
    'password': false,
    'channelName': false
  },
  'settings': {
    'voiceMode': 'vad',
    'pttKey': 'ctrl + shift',
    'vadLevel': 0.3,
    'toolbarVertical': false,
    'showAvatars': 'always',
    'userCountInChannelName': false,
    'audioBitrate': 40000,
    'samplesPerPacket': 960
  },
  'defaults': {
    'address': 'mc.domain.com',
    'port': '50732/_voice',
    'token': 'MumbleWebAccessToken',
    'username': 'web-',
    'password': '',
    'webrtc': 'auto',
    'joinDialog': false,
    'matrix': false,
    'avatarurl': '',
    'theme': 'MetroMumbleDark',
    'startMute': true,
    'startDeaf': false
  }
};

Obviously change what needs to be changed. You need to add the Access Token you created from the mumble client to this so it's authorized to chat on your mumble server. This will present the server name and port, but mainly the username with a "web-" prefix for them to append their name to. Pay attention to port 50732 there, that is our apapche ssl port and it will be used in the next step, if you use a different port, put that there.

First of all, you need the following modules enabled in apache:

Loaded Modules:
 core_module (static)
 so_module (static)
 watchdog_module (static)
 http_module (static)
 log_config_module (static)
 logio_module (static)
 version_module (static)
 unixd_module (static)
 access_compat_module (shared)
 alias_module (shared)
 auth_basic_module (shared)
 authn_core_module (shared)
 authn_file_module (shared)
 authz_core_module (shared)
 authz_host_module (shared)
 authz_user_module (shared)
 autoindex_module (shared)
 deflate_module (shared)
 dir_module (shared)
 env_module (shared)
 filter_module (shared)
 headers_module (shared)
 mime_module (shared)
 mpm_event_module (shared)
 negotiation_module (shared)
 proxy_module (shared)
 proxy_http_module (shared)
 proxy_wstunnel_module (shared)
 reqtimeout_module (shared)
 rewrite_module (shared)
 setenvif_module (shared)
 socache_shmcb_module (shared)
 ssl_module (shared)
 status_module (shared)

Then, you should create a file in /etc/apache2/sites-available called mumble.conf and put the following in it:


        ServerName mc.domain.com
        ServerAdmin username@domain.com

        DocumentRoot /var/www/mumble

        
                ProxyPass ws://127.0.0.1:50731/
                ProxyPassReverse ws://127.0.0.1:50731/
        

        ErrorLog ${APACHE_LOG_DIR}/error.log
        LogLevel warn
        CustomLog ${APACHE_LOG_DIR}/ssl_access.log combined
        SSLEngine on
        SSLCertificateFile /etc/apache2/ssl/mc.domain.com.pem
        SSLCertificateKeyFile /etc/apache2/ssl/mc.domain.com.key
        SSLCACertificateFile /etc/apache2/ssl/mc.domain.com.intermediate.crt

Remember, I'm using the nonstandard port of 50732 for https/wss for this app, and mumble-web-proxy is listening on 50731. Update as you see fit. This not only hosts the web app, but uses apache as your websocket reverse proxy.

Load this site with "a2ensite mumble.conf" and restart apache.

Next, make a folder /etc/matterbridge and get the latest release executable (linux 64-bit most likely) from here and put it in that folder. Make it executable and change the ownership to mumble-server. You will want to create a client certificate and key with the following command: openssl req -x509 -newkey rsa:2048 -nodes -days 10000 -keyout mumble.key -out mumble.crt then make sure to put them in that folder as well, once again owned by mumble-server. Lastly, we need to make the file /etc/matterbridge/matterbridge.toml but this is the file that will have the most deviation. You need to create a gateway for each chat platform you want to link, then link those gateways at the bottom. Here is what mine looks like:

[general]

[mumble.domain]
        Server = "mc.domain.com:50730"
        Nick = "MC"
        TLSClientCertificate="mumble.crt"
        TLSClientKey="mumble.key"
        SkipTLSVerify=true
        RemoteNickFormat="[{NICK}] "

[api.myapi]
        BindAddress="127.0.0.1:4242"
        Buffer=1000
        RemoteNickFormat="{NICK}"
        PrefixMessagesWithNick=true
        Token="domainMatterBridgeAPIToken"

[[gateway]]
        name="domainMumbleGateway"
        enable=true

        [[gateway.inout]]
                account="mumble.domain"
                channel="1"

        [[gateway.inout]]
                account="api.myapi"
                channel="api"

Update your domain name wherever you see it. You're pointing directly at your mumble server and not the web proxy or anything, so pay attention to the port. The matterbridge API is only exposed to localhost, so the default port is fine. You should make an API token for the bukkit plugin to validate with. I used the mumble nic "MC" with the prefix "[{NICK}] " so in mumble, messages show up like this: "MC: [playername] hi"

To run this, I made another systemd unit file at /etc/systemd/system/matterbridge.service:

[Unit]
Description=Matterbridge Chat Connector
After=network.target
After=mumble-server.service
After=mumble-web-proxy.service

[Service]
Type=simple
WorkingDirectory=/etc/matterbridge
PrivateUsers=true 
User=mumble-server
Group=mumble-server
ProtectSystem=full 
ProtectHome=true 
ProtectKernelTunables=true 
ProtectKernelModules=true 
ProtectControlGroups=true 

ExecStart=/etc/matterbridge/matterbridge -conf /etc/matterbridge/matterbridge.toml

Restart=on-failure
RestartSec=60s

[Install]
WantedBy=multi-user.target

Another "systemctl daemon-reload; systemctl enable matterbridge.service; systemctl start matterbridge.service" and it'll be running, logging to /var/log/syslog. You can see this with "tail -f /var/log/syslog | grep matterbridge". You will also see the "MC" user in your mumble server, you should go into your mumble client with the admin account, right click "MC" and register them so no one else can use that name.

If you run "netstat -tulpn" you'll now see listeners on 50730, 50731, 50732, and 4242 (or whatever ports you're using). That's perfect, now we're ready to install the bukkit plugin.

Get it from the link above, run it, it'll create a config directory with a file. Edit that file, then restart your server. Here's the contents of plugins/MatterBukkit/config.yml:

bridge:
  # URL to use to connect to MatterBridge
  url: http://localhost:4242

  # Name of the gateway to which MatterBukkit should connect to
  gateway: domainMumbleGateway

  # Token to use for MatterBridge authentication (optional)
  token: domainMatterBridgeAPIToken

incoming:
  # Print chat messages received from MatterBridge to all players
  enable: true

  # Format for incoming chat messages
  # Placeholders: %username% (name of the user who sent the message), %text% (content of the message)
  format: "%username% %text%"

outgoing:
  # URL to use for avatars in outgoing messages (requires a protocol on the other side of MatterBridge to support avatars)
  # Placeholders: %playername% (name of the player), %uuid% (UUID of the player)
  avatar-url: "https://crafatar.com/avatars/%uuid%"

  # URL to use for avatars in outgoing system messages (i.e. death messages, player joins/quits, advancements and level up)
  system-avatar-url: null

  # Username to use for outgoing system messages
  system-username: "System"

  chat:
    # Send player chat messages to MatterBridge
    enable: true

  death:
    # Send death messages to MatterBridge
    enable: true

    # Format for death messages sent to MatterBridge
    # Placeholders: %playername% (name of the player), %death-message% (the death message as shown to the player, i.e. "Playername was blown up by Creeper")
    format: "%death-message%"

  advancement:
    # Send advancement messages to MatterBridge
    enable: true

    # Format for advancement messages sent to MatterBridge
    # Placeholders: %playername% (name of the player), %advancement% (name of the advancement as defined in advancements.yml)
    format: "%playername% has made the advancement [%advancement%]"

  level-up:
    # Send level up messages to MatterBridge
    enable: true

    # Minimum level to reach before sending a level up message
    minimum-level: 0

    # Only send level up messages every n levels (i.e. "5" would send a message once the player reached level 5, 10, 15, etc.)
    modulus: 25

    # Format for level up messages sent to MatterBridge
    # Placeholders: %playername% (name of the player), %old-level% (the level the player was at before reaching the new level), %new-level% (the reached level)
    format: "%playername% reached level %new-level%"

  join:
    # Send join messages to MatterBridge (i.e. "Playername joined the server")
    enable: true

  quit:
    # Send quit messages to MatterBridge (i.e. "Playername left the server")
    enable: true

You can see a lot of the config here, adjust it to your needs.

And that's it! Took me a few days to get set up, but now that it's done it's very very useful and I'm super happy with it, and hope that this can provide some help to anyone else looking to set things up. There's many easier ways to do this with things like Discord plugins, but if you just want something self-hosted and open source, this is a great setup.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Improving the Motorola Blink Baby Monitor/Camera (Part 5)

So I took the camera apart and took a high-res picture of the PCB. The wifi sub-board is soldered on so I won't remove it yet to expose the ARM CPU. That being said, maybe there's a clue on here for how to mount and write to the flash memory:

IMG_2312

Read more »
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Improving the Motorola Blink Baby Monitor/Camera (Part 4)

I screwed up.

I finally did it, I figured out the commands to do a custom firmware, and I tried to flash it...now the camera is UNRESPONSIVE. It boots, but no network, the LED is on, can't talk to it, nada. I'm working on my backout plan now :) Hey, that's the price of hacking. Nevertheless, I've learned a TON which is worth sharing.

Below is the set of commands I used to generate my custom firmware. The original firmware is a tar.gz, which contains conprog.bin and rootfs.bin.gz, then rootfs.bin.gz unpacks into rootfs.bin which can be mounted with:

sudo mount -t romfs -o loop rootfs.bin /mnt/rootfs
Read more »
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -