Skip to content
This repository was archived by the owner on Apr 27, 2019. It is now read-only.

Commit 2a2501a

Browse files
committed
Merge pull request #130 from kharidiron/master
Hot fix for players escaping wrapper, party chat feature, fixed login error in webgui, bugfixes in database, more logging and debugging, better regular expression for matching color codes in names, and minor fixes in packet definitions.
2 parents af568ab + f16232d commit 2a2501a

15 files changed

Lines changed: 306 additions & 29 deletions

File tree

README.Protocol668.md

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Changes regarding Protocol 668
2+
3+
With the release of Starbound protocol version 668, you may have noticed that
4+
the account/password system is no longer working. This is resulting from
5+
Chucklefish updating their authentication system.
6+
7+
While this does break some of StarryPy's functionality, the solution is not to
8+
fix StarryPy, but instead to change how you think about 'authentication' in
9+
Starbound.
10+
11+
For the impatient, please scroll down to the **Fixing the Problem** section for
12+
the cut-and dry solution.
13+
14+
15+
## Changes to **starbound.config**
16+
17+
As @alex-lawson (aka - metadept) pointed out in the news post
18+
http://playstarbound.com/february-17-server-configuration-changes/
19+
there were some changes in how **starbound.config** is structured. As a callout
20+
here, the changes are:
21+
22+
```
23+
"allowAnonymousConnections" : false,
24+
"allowAdminCommands" : true,
25+
"allowAdminCommandsFromAnyone" : false,
26+
"bannedIPs" : [ ],
27+
"bannedUuids" : [ ],
28+
29+
...
30+
31+
"serverUsers" : {
32+
"fred" : {
33+
"admin" : true,
34+
"password" : "hunter2"
35+
},
36+
"george" : {
37+
"admin" : false,
38+
"password" : "swordfish"
39+
}
40+
},
41+
```
42+
43+
In order to adapt this to StarryPy, we need to change the way we think about
44+
authentication. Previously, most servers would use a shared, public or shared,
45+
private password. This, combined with a UUID and a name would uniquely identify
46+
a character. The flaw with this system, however, was the assumption that a
47+
character's UUID would remain obfuscated from other users, ensuring uniqueness.
48+
49+
This however, is by far, no longer the case as UUID numbers are now quite easy
50+
to collect, and thus to reuse and *'spoof'* other characters. Particularly for
51+
character's with administrative privileges, this was a concern that needed to be
52+
addressed.
53+
54+
Enter git commit https://github.com/kharidiron/StarryPy/commit/c371ade0301be369c8f4c9baedcc5e9685fc8633
55+
where I added an additional variable called `admin_ss` for tracking if an
56+
authenticated user also provided an additional *shared secret* password for
57+
accessing privileged functions. It was then, up to the server administrators to
58+
make sure their admins were informed of the shared secret. This would not
59+
prevent UUID spoofers from doing their spoofing, but it *WOULD* prevent them
60+
from being able to run admin commands. This sort of system is termed a 'dead
61+
man's switch'.
62+
63+
Fast-forward to release of protocol 668, and now people entering the shared
64+
secret password are being greeted with 'No such account or incorrect password.'
65+
66+
Now what were we to do?
67+
68+
Originally I was starting to work out how to re-write the code to account for
69+
new user accounts, and access levels, and such... a minor headache, and some
70+
time debt to say the least. But then a user in the IRC channel
71+
(gandalfthecolorb) pointed out that no changes were actually needed. Instead,
72+
we need to just add an account to the Starbound server configuration to act as
73+
the collective 'rolls' for all the admin levels. An easy, and elegant solution
74+
that requires no changing of code on our end, and still maintains the same level
75+
of security for the servers.
76+
77+
So, now on to fixing the problem.
78+
79+
80+
## Fixing the Problem
81+
82+
#### tl;dr
83+
84+
Using the same `admin_ss` password that users set before, along with whatever
85+
server password StarryPy owners want to ship, we simply need to update the
86+
starbound.config file to match:
87+
88+
```
89+
"serverUsers" : {
90+
"<admin_ss goes here>" : {
91+
"admin" : <can be true or false, per your needs>,
92+
"password" : "<either continue using your old password, or set a new one>"
93+
}
94+
}
95+
```
96+
97+
And that is it. If you choose to continue using a shared public password, you
98+
would need to add an additional section for this, and then provide all of your
99+
users with a generic 'account' to log into (from metadept's example, this would
100+
be *'guest'*). You would also need to be sure to set `allowAnonymousConnections`
101+
to `false` as well.
102+
103+
104+
#### An additional note regarding commands
105+
106+
StarryPy can be configured to either block, or allow vanilla server commands,
107+
by changing the option `command_prefix` to something other than `/`. Some
108+
suggestions have been for `!` instead. This, on its own, does not enable the
109+
Starbound `/admin` commands, but conversely, can prevent you from using them if
110+
you leave the prefix in its default state.

config.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ def __call__(cls, *args, **kwargs):
1919
class ConfigurationManager(object):
2020
__metaclass__ = Singleton
2121
logger = logging.getLogger("starrypy.config.ConfigurationManager")
22+
log_format = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s # %(message)s')
23+
logfile_handle = logging.FileHandler("config.log")
24+
logfile_handle.setLevel(9)
25+
logger.addHandler(logfile_handle)
26+
logfile_handle.setFormatter(log_format)
2227

2328
def __init__(self):
2429
default_config_path = path.preauthChild("config/config.json.default")
@@ -27,7 +32,8 @@ def __init__(self):
2732
try:
2833
with default_config_path.open() as default_config:
2934
default = json.load(default_config)
30-
except ValueError:
35+
except ValueError as e:
36+
print "Error: %s" % e
3137
self.logger.critical("The configuration defaults file (config.json.default) contains invalid JSON. Please run it against a JSON linter, such as http://jsonlint.com. Shutting down." )
3238
sys.exit()
3339
else:
@@ -39,7 +45,8 @@ def __init__(self):
3945
with self.config_path.open() as c:
4046
config = json.load(c)
4147
self.config = recursive_dictionary_update(default, config)
42-
except ValueError:
48+
except ValueError as e:
49+
print "Error: %s" % e
4350
self.logger.critical("The configuration file (config.json) contains invalid JSON. Please run it against a JSON linter, such as http://jsonlint.com. Shutting down.")
4451
sys.exit()
4552
else:
@@ -98,4 +105,4 @@ def __setattr__(self, key, value):
98105
self.save
99106
else:
100107
self.config[key] = value
101-
self.save()
108+
self.save()

config/config.json.default

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,8 +191,8 @@
191191
"admin_ss": "tester",
192192
"auto_activate": true,
193193
"name_removal_regexes": [
194-
"\\^#[\\w]+;",
195-
"[^ \\w]+"
194+
"\\^\\w+;|\\^#\\w+;|\\W",
195+
"\\s\\s+"
196196
]
197197
},
198198
"players_plugin": {

packets/packet_types.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -257,12 +257,25 @@ def _decode(self, obj, context):
257257
GreedyRange(star_string("requests")))
258258

259259
# (14) - ClientContextUpdate
260+
#client_context_update = lambda name="client_context": Struct(name,
261+
# VLQ("length"),
262+
# Byte("arguments"),
263+
# Array(lambda ctx: ctx.arguments,
264+
# Struct("key",
265+
# Variant("value"))))
260266
client_context_update = lambda name="client_context": Struct(name,
261267
VLQ("length"),
262-
Byte("arguments"),
263-
Array(lambda ctx: ctx.arguments,
264-
Struct("key",
265-
Variant("value"))))
268+
Peek(Byte("a")),
269+
If(lambda ctx: ctx["a"] == 0,
270+
Struct("junk",
271+
Padding(1),
272+
VLQ("extra_length"))),
273+
If(lambda ctx: ctx["a"] > 8,
274+
Struct("junk2",
275+
VLQ("extra_length"))),
276+
VLQ("subpackets"),
277+
Array(lambda ctx: ctx.subpackets,
278+
(Variant("subpacket"))))
266279

267280
# (15) - WorldStart
268281
world_start = lambda name="world_start": Struct(name,
@@ -409,5 +422,5 @@ def _decode(self, obj, context):
409422
properties=[Container(key=k, value=Container(type="SVLQ", data=v)) for k, v in dictionary.items()]))
410423

411424
# (53) - Heartbeat
412-
heartbeat = lambda name="heartbeat": Structure(name,
413-
UBInt64("remote_step"))
425+
heartbeat = lambda name="heartbeat": Struct(name,
426+
VLQ("remote_step"))

plugins/core/admin_commands_plugin/admin_command_plugin.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -281,14 +281,14 @@ def item(self, data):
281281
item_count = item[1]
282282
else:
283283
item_count = 1
284-
give_item_to_player(target_protocol, item_name, item_count)
284+
given = give_item_to_player(target_protocol, item_name, item_count)
285285
target_protocol.send_chat_message(
286286
"%s^green; has given you: ^yellow;%s^green; (count: ^cyan;%s^green;)" % (
287-
self.protocol.player.colored_name(self.config.colors), item_name, item_count))
287+
self.protocol.player.colored_name(self.config.colors), item_name, given))
288288
self.protocol.send_chat_message("Sent ^yellow;%s^green; (count: ^cyan;%s^green;) to %s" % (
289-
item_name, item_count, target_player.colored_name(self.config.colors)))
289+
item_name, given, target_player.colored_name(self.config.colors)))
290290
self.logger.info("%s gave %s %s (count: %s)", self.protocol.player.name, name, item_name,
291-
item_count)
291+
given)
292292
else:
293293
self.protocol.send_chat_message("You have to give an item name.")
294294
else:

plugins/core/player_manager/manager.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,23 +45,44 @@ def migrate_db(config):
4545
dbcon = sqlite3.connect(path.preauthChild(config.player_db).path)
4646
dbcur = dbcon.cursor()
4747

48+
res = dbcur.execute("PRAGMA user_version;")
49+
db_version = res.fetchone()[0]
50+
try:
51+
if db_version is 0:
52+
dbcur.execute('DROP TABLE `ips`;')
53+
dbcur.execute("PRAGMA user_version = 1;")
54+
logger.info("Migrating DB from version 0 to version 1.")
55+
except sqlite3.OperationalError, e:
56+
logger.info("No DB exists. Will create a new one.")
57+
4858
try:
4959
dbcur.execute('SELECT org_name FROM players;')
5060
except sqlite3.OperationalError, e:
5161
if "column" in str(e):
62+
logger.info("Updating DB to include org_name column.")
5263
dbcur.execute('ALTER TABLE `players` ADD COLUMN `org_name`;')
5364
dbcur.execute('UPDATE `players` SET `org_name`=`name`;')
5465
dbcon.commit()
5566

67+
try:
68+
dbcur.execute('SELECT party_id FROM players;')
69+
except sqlite3.OperationalError, e:
70+
if "column" in str(e):
71+
logger.info("Updating DB to include party_id column.")
72+
dbcur.execute('ALTER TABLE `players` ADD COLUMN `party_id`;')
73+
dbcur.execute('UPDATE `players` SET `party_id`="";')
74+
dbcon.commit()
75+
5676
try:
5777
dbcur.execute('SELECT admin_logged_in FROM players;')
5878
except sqlite3.OperationalError, e:
5979
if "column" in str(e):
80+
logger.info("Updating DB to include admin_logged_in column.")
6081
dbcur.execute('ALTER TABLE `players` ADD COLUMN `admin_logged_in`;')
6182
dbcur.execute('UPDATE `players` SET `admin_logged_in`=0;')
6283
dbcon.commit()
63-
dbcon.close()
6484

85+
dbcon.close()
6586

6687
logger = logging.getLogger("starrypy.player_manager.manager")
6788

@@ -175,6 +196,7 @@ class Player(Base):
175196
admin_logged_in = Column(Boolean)
176197
protocol = Column(String)
177198
client_id = Column(Integer)
199+
party_id = Column(String)
178200
ip = Column(String)
179201
plugin_storage = Column(JSONEncodedDict, default=dict())
180202
planet = Column(String)
@@ -233,6 +255,7 @@ class Ban(Base):
233255
class PlayerManager(object):
234256
def __init__(self, config):
235257
self.config = config
258+
migrate_db(self.config)
236259
logger.info("Loading player database.")
237260
try:
238261
self.engine = create_engine('sqlite:///%s' % path.preauthChild(self.config.player_db).path)
@@ -244,6 +267,7 @@ def __init__(self, config):
244267
for player in session.query(Player).filter_by(logged_in=True).all():
245268
player.logged_in = False
246269
player.admin_logged_in = False
270+
player.party_id = ""
247271
player.protocol = None
248272
session.commit()
249273

@@ -280,7 +304,8 @@ def fetch_or_create(self, uuid, name, org_name, admin_logged_in, ip, protocol=No
280304
if player.name != name:
281305
logger.info("Detected username change.")
282306
player.name = name
283-
if ip not in player.ips:
307+
if not session.query(IPAddress).filter_by(uuid=uuid, ip=ip).first():
308+
logger.debug("New ip address detected for user. Adding to database.")
284309
player.ips.append(IPAddress(ip=ip))
285310
player.ip = ip
286311
player.protocol = protocol
@@ -295,6 +320,7 @@ def fetch_or_create(self, uuid, name, org_name, admin_logged_in, ip, protocol=No
295320
admin_logged_in=False,
296321
protocol=protocol,
297322
client_id=-1,
323+
party_id="",
298324
ip=ip,
299325
planet="",
300326
on_ship=True)

plugins/core/player_manager/plugin.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def check_logged_in(self):
3232
if player.protocol not in self.factory.protocols.keys():
3333
player.logged_in = False
3434
player.admin_logged_in = False
35+
player.party_id = ""
3536

3637
def on_client_connect(self, data):
3738
client_data = client_connect().parse(data.data)
@@ -99,7 +100,9 @@ def reject_with_reason(self, reason):
99100
Container(
100101
success=False,
101102
client_id=0,
102-
reject_reason=reason
103+
reject_reason=reason,
104+
celestial_info_exists=False,
105+
celestial_data=None
103106
)
104107
) + unlocked_sector_magic
105108
)
@@ -114,6 +117,7 @@ def on_connect_response(self, data):
114117
else:
115118
self.protocol.player.client_id = connection_parameters.client_id
116119
self.protocol.player.logged_in = True
120+
self.protocol.player.party_id = ""
117121
self.logger.info("Player %s (UUID: %s, IP: %s) logged in" % (
118122
self.protocol.player.name, self.protocol.player.uuid,
119123
self.protocol.transport.getPeer().host))
@@ -256,4 +260,4 @@ def format_player_response(self, players):
256260
players[:25]]))
257261
self.protocol.send_chat_message(
258262
"And %d more. Narrow it down with SQL like syntax. Feel free to use a *, it will be replaced appropriately." % (
259-
len(players) - 25))
263+
len(players) - 25))

plugins/fuelgiver/fuelgiver_plugin.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ def fuel(self, data):
2828
return
2929
if not 'last_given_fuel' in my_storage or float(my_storage['last_given_fuel']) <= float(time()) - 86400:
3030
my_storage['last_given_fuel'] = str(time())
31-
give_item_to_player(self.protocol, "fillerup", 1)
31+
given = give_item_to_player(self.protocol, "fillerup", 1)
3232
self.protocol.player.storage = my_storage
3333
self.protocol.send_chat_message("You were given a daily fuel supply! Now go explore ;)")
3434
self.logger.info("Gave fuel to %s.", self.protocol.player.name)
3535
else:
36-
self.protocol.send_chat_message("^red;No... -.- Go mining!")
36+
self.protocol.send_chat_message("^red;No... -.- Go mining!")

plugins/new_player_greeter_plugin/new_player_greeter_plugin.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def after_world_start(self, data):
2525

2626
def give_items(self):
2727
for item in self.config.plugin_config["items"]:
28-
give_item_to_player(self.protocol, item[0], item[1])
28+
given = give_item_to_player(self.protocol, item[0], item[1])
2929

3030
def send_greetings(self):
31-
self.protocol.send_chat_message(self.config.plugin_config["message"])
31+
self.protocol.send_chat_message(self.config.plugin_config["message"])
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from partychat_plugin import PartyChatPlugin

0 commit comments

Comments
 (0)