Merge branch 'master' into develop

Conflicts:
	scripts/run-turn.lua
	src/buildno.h
	src/kernel/group.c
	src/kernel/save.c
	src/kernel/save.test.c
	src/kernel/version.h
	src/util/password.c
	src/util/password.test.c
	tests/run-turn.sh
This commit is contained in:
Enno Rehling 2016-02-23 10:47:33 +01:00
commit e6f3dc8b85
41 changed files with 1051 additions and 147 deletions

View File

@ -69,6 +69,7 @@ add_subdirectory (storage)
add_subdirectory (iniparser) add_subdirectory (iniparser)
add_subdirectory (quicklist) add_subdirectory (quicklist)
add_subdirectory (critbit) add_subdirectory (critbit)
add_subdirectory (process)
add_subdirectory (src eressea) add_subdirectory (src eressea)
install(DIRECTORY res conf DESTINATION ${CMAKE_INSTALL_PREFIX} FILES_MATCHING PATTERN "*.xml") install(DIRECTORY res conf DESTINATION ${CMAKE_INSTALL_PREFIX} FILES_MATCHING PATTERN "*.xml")
install(DIRECTORY res conf DESTINATION ${CMAKE_INSTALL_PREFIX} FILES_MATCHING PATTERN "*.json") install(DIRECTORY res conf DESTINATION ${CMAKE_INSTALL_PREFIX} FILES_MATCHING PATTERN "*.json")

8
process/CMakeLists.txt Normal file
View File

@ -0,0 +1,8 @@
install(PROGRAMS create-orders backup-eressea run-turn send-zip-report
send-bz2-report compress.py compress.sh epasswd.py orders-process
checkpasswd.py sendreport.sh orders-accept DESTINATION bin)
install(DIRECTORY cron/ DESTINATION bin USE_SOURCE_PERMISSIONS
FILES_MATCHING PATTERN "*.cron")
install(DIRECTORY procmail DESTINATION share)

20
process/checkpasswd.py Executable file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env python
import sys, re
from epasswd import EPasswd
if len(sys.argv)<4:
sys.exit(-2)
passfile=sys.argv[1]
myfaction=sys.argv[2]
mypasswd=sys.argv[3]
if mypasswd[0]=='"':
mypasswd=mypasswd[1:len(mypasswd)-1]
pw_data=EPasswd(passfile)
if pw_data.fac_exists(myfaction):
if pw_data.check(myfaction, mypasswd):
sys.exit(0)
sys.exit(-1)

50
process/epasswd.py Executable file
View File

@ -0,0 +1,50 @@
#!/usr/bin/python
from string import split
from string import strip
from string import lower
import subprocess
class EPasswd:
def _check_apr1(self, pwhash, pw):
spl = split(pwhash, '$')
salt = spl[2]
hash = subprocess.check_output(['openssl', 'passwd', '-apr1', '-salt', salt, pw]).decode('utf-8').strip()
return hash==pwhash
def __init__(self, file):
self.data = {}
try:
fp = open(file,"r")
except:
fp = None
if fp != None:
while True:
line = fp.readline()
if not line: break
line = strip(line)
[id, email, passwd] = split(line, ":")[0:3]
lc_id = lower(id)
self.data[lc_id] = {}
self.data[lc_id]["id"] = id
self.data[lc_id]["email"] = email
self.data[lc_id]["passwd"] = passwd
fp.close()
def check(self, id, passwd):
pw = self.get_passwd(id)
if pw[0:6]=='$apr1$':
return self._check_apr1(pw, passwd)
return pw == passwd
def get_passwd(self, id):
return self.data[lower(id)]["passwd"]
def get_email(self, id):
return self.data[lower(id)]["email"]
def get_canon_id(self, id):
return self.data[lower(id)]["id"]
def fac_exists(self, id):
return self.data.has_key(lower(id))

View File

@ -1,2 +1,368 @@
#/bin/.sh #!/usr/bin/env python
grep -v '>From' | $HOME/src/scripts/bin/orders-accept $* # -*- coding: iso-8859-1 -*-
from email.Utils import parseaddr
from email.Parser import Parser
from os import mkdir, rename, stat, utime, unlink, symlink
from os.path import exists
from re import compile, IGNORECASE
from stat import ST_MTIME
from string import upper, split, replace
import logging
from sys import argv, stdin, exit
from time import ctime, sleep, time
from socket import gethostname
from rfc822 import parsedate_tz, mktime_tz
LOG_FILENAME='/home/eressea/log/orders.log'
prefix = 'turn-'
hostname = gethostname()
# base directory for all your games:
rootdir = "/home/eressea"
orderbase = "orders.dir"
sendmail = True
# maximum number of reports per sender:
maxfiles = 20
# write headers to file?
writeheaders = True
# reject all html email?
rejecthtml = True
games = [
{
"from" : "Eressea Server <eressea-server@eressea.de>",
"prefix" : "Eressea"
},
{
"from" : "Eressea Server <eressea-server@eressea.de>",
"prefix": "E3"
},
{
"from" : "Eressea Server <eressea-server@eressea.de>",
"prefix": "E4"
},
]
def unlock_file(filename):
try:
unlink(filename+".lock")
except:
print "could not unlock %s.lock, file not found" % filename
def lock_file(filename):
i = 0
wait = 1
if not exists(filename):
file=open(filename, "w")
file.close()
while True:
try:
symlink(filename, filename+".lock")
return
except:
i = i+1
if i == 5: unlock_file(filename)
sleep(wait)
wait = wait*2
messages = {
"multipart-en" :
"ERROR: The orders you sent contain no plaintext. " \
"The Eressea server cannot process orders containing HTML " \
"or invalid attachments, which are the reasons why this " \
"usually happens. Please change the settings of your mail " \
"software and re-send the orders.",
"multipart-de" :
"FEHLER: Die von dir eingeschickte Mail enthält keinen " \
"Text. Evtl. hast Du den Zug als HTML oder als anderweitig " \
"ungültig formatierte Mail ingeschickt. Wir können ihn " \
"deshalb nicht berücksichtigen. Schicke den Zug nochmals " \
"als reinen Text ohne Formatierungen ein.",
"maildate-de":
"Es erreichte uns bereits ein Zug mit einem späteren " \
"Absendedatum (%s > %s). Entweder ist deine " \
"Systemzeit verstellt, oder ein Zug hat einen anderen Zug von " \
"dir auf dem Transportweg überholt. Entscheidend für die " \
"Auswertungsreihenfolge ist das Absendedatum, d.h. der Date:-Header " \
"deiner Mail.",
"maildate-en":
"The server already received an order file that was sent at a later " \
"date (%s > %s). Either your system clock is wrong, or two messages have " \
"overtaken each other on the way to the server. The order of " \
"execution on the server is always according to the Date: header in " \
"your mail.",
"nodate-en":
"Your message did not contain a valid Date: header in accordance with RFC2822.",
"nodate-de":
"Deine Nachricht enthielt keinen gueltigen Date: header nach RFC2822.",
"error-de":
"Fehler",
"error-en":
"Error",
"warning-de":
"Warnung",
"warning-en":
"Warning",
"subject-de":
"Befehle angekommen",
"subject-en":
"orders received"
}
# return 1 if addr is a valid email address
def valid_email(addr):
rfc822_specials = '/()<>@,;:\\"[]'
# First we validate the name portion (name@domain)
c = 0
while c < len(addr):
if addr[c] == '"' and (not c or addr[c - 1] == '.' or addr[c - 1] == '"'):
c = c + 1
while c < len(addr):
if addr[c] == '"': break
if addr[c] == '\\' and addr[c + 1] == ' ':
c = c + 2
continue
if ord(addr[c]) < 32 or ord(addr[c]) >= 127: return 0
c = c + 1
else: return 0
if addr[c] == '@': break
if addr[c] != '.': return 0
c = c + 1
continue
if addr[c] == '@': break
if ord(addr[c]) <= 32 or ord(addr[c]) >= 127: return 0
if addr[c] in rfc822_specials: return 0
c = c + 1
if not c or addr[c - 1] == '.': return 0
# Next we validate the domain portion (name@domain)
domain = c = c + 1
if domain >= len(addr): return 0
count = 0
while c < len(addr):
if addr[c] == '.':
if c == domain or addr[c - 1] == '.': return 0
count = count + 1
if ord(addr[c]) <= 32 or ord(addr[c]) >= 127: return 0
if addr[c] in rfc822_specials: return 0
c = c + 1
return count >= 1
# return the replyto or from address in the header
def get_sender(header):
replyto = header.get("Reply-To")
if replyto is None:
replyto = header.get("From")
if replyto is None: return None
x = parseaddr(replyto)
return x[1]
# return first available filename basename,[0-9]+
def available_file(dirname, basename):
ver = 0
maxdate = 0
filename = "%s/%s,%s,%d" % (dirname, basename, hostname, ver)
while exists(filename):
maxdate = max(stat(filename)[ST_MTIME], maxdate)
ver = ver + 1
filename = "%s/%s,%s,%d" % (dirname, basename, hostname, ver)
if ver >= maxfiles:
return None, None
return maxdate, filename
def formatpar(string, l=76, indent=2):
words = split(string)
res = ""
ll = 0
first = 1
for word in words:
if first == 1:
res = word
first = 0
ll = len(word)
else:
if ll + len(word) > l:
res = res + "\n"+" "*indent+word
ll = len(word) + indent
else:
res = res+" "+word
ll = ll + len(word) + 1
return res+"\n"
def store_message(message, filename):
outfile = open(filename, "w")
outfile.write(message.as_string())
outfile.close()
return
def write_part(outfile, part):
charset = part.get_content_charset()
payload = part.get_payload(decode=True)
if charset is None:
charset = "latin1"
try:
msg = payload.decode(charset, "ignore")
except:
msg = payload
charset = None
try:
utf8 = msg.encode("utf-8", "ignore")
outfile.write(utf8)
except:
outfile.write(msg)
return False
outfile.write("\n");
return True
def copy_orders(message, filename, sender):
# print the header first
if writeheaders:
from os.path import split
dirname, basename = split(filename)
dirname = dirname + '/headers'
if not exists(dirname): mkdir(dirname)
outfile = open(dirname + '/' + basename, "w")
for name, value in message.items():
outfile.write(name + ": " + value + "\n")
outfile.close()
found = False
outfile = open(filename, "w")
if message.is_multipart():
for part in message.get_payload():
content_type = part.get_content_type()
logger.debug("found content type %s for %s" % (content_type, sender))
if content_type=="text/plain":
if write_part(outfile, part):
found = True
else:
charset = part.get_content_charset()
logger.error("could not write text/plain part (charset=%s) for %s" % (charset, sender))
else:
if write_part(outfile, message):
found = True
else:
charset = message.get_content_charset()
logger.error("could not write text/plain message (charset=%s) for %s" % (charset, sender))
outfile.close()
return found
# create a file, containing:
# game=0 locale=de file=/path/to/filename email=rcpt@domain.to
def accept(game, locale, stream, extend=None):
global rootdir, orderbase
if extend is not None:
orderbase = orderbase + ".pre-" + extend
gamename = games[game-2]["prefix"]
gamedir = rootdir+"/eressea/game-%d" % (game, )
savedir = gamedir+"/"+orderbase
# check if it's one of the pre-sent orders.
# create the save-directories if they don't exist
if not exists(gamedir): mkdir(gamedir)
if not exists(savedir): mkdir(savedir)
# parse message
message = Parser().parse(stream)
sender = get_sender(message)
logger = logging.getLogger(sender)
# write syslog
if sender is None or valid_email(sender)==0:
logger.warning("invalid email address: " + str(sender))
return -1
logger.info("received orders from " + sender)
# get an available filename
lock_file(gamedir + "/orders.queue")
maxdate, filename = available_file(savedir, prefix + sender)
if filename is None:
logger.warning("more than " + str(maxfiles) + " orders from " + sender)
return -1
# copy the orders to the file
text_ok = copy_orders(message, filename, sender)
unlock_file(gamedir + "/orders.queue")
warning, msg, fail = None, "", False
maildate = message.get("Date")
if maildate != None:
turndate = mktime_tz(parsedate_tz(maildate))
utime(filename, (turndate, turndate))
logger.debug("mail date is '%s' (%d)" % (maildate, turndate))
if turndate < maxdate:
logger.warning("inconsistent message date " + sender)
warning = " (" + messages["warning-" + locale] + ")"
msg = msg + formatpar(messages["maildate-" + locale] % (ctime(maxdate),ctime(turndate)), 76, 2) + "\n"
else:
logger.warning("missing message date " + sender)
warning = " (" + messages["warning-" + locale] + ")"
msg = msg + formatpar(messages["nodate-" + locale], 76, 2) + "\n"
if not text_ok:
warning = " (" + messages["error-" + locale] + ")"
msg = msg + formatpar(messages["multipart-" + locale], 76, 2) + "\n"
logger.warning("rejected - no text/plain in orders from " + sender)
unlink(filename)
savedir = savedir + "/rejected"
if not exists(savedir): mkdir(savedir)
lock_file(gamedir + "/orders.queue")
maxdate, filename = available_file(savedir, prefix + sender)
store_message(message, filename)
unlock_file(gamedir + "/orders.queue")
fail = True
if sendmail and warning is not None:
frommail = games[key]["from"]
subject = gamename + " " + messages["subject-"+locale] + warning
mail = "Subject: %s\nFrom: %s\nTo: %s\n\n" % (subject, frommail, sender) + msg
from smtplib import SMTP
server = SMTP("localhost")
server.sendmail(frommail, sender, mail)
server.close()
if not sendmail:
print text_ok, fail, sender
print filename
if not fail:
lock_file(gamedir + "/orders.queue")
queue = open(gamedir + "/orders.queue", "a")
queue.write("email=%s file=%s locale=%s game=%s\n" % (sender, filename, locale, game))
queue.close()
unlock_file(gamedir + "/orders.queue")
logger.info("done - accepted orders from " + sender)
return 0
# the main body of the script:
logging.basicConfig(level=logging.DEBUG, filename=LOG_FILENAME)
logger = logging
delay=None # TODO: parse the turn delay
try:
game = int(argv[1])
except:
game = argv[1]
if game[:3]=='e3a':
game = 3
elif game[:7]=='eressea':
game = 2
locale = argv[2]
infile = stdin
if len(argv)>3:
infile = open(argv[3], "r")
retval = accept(game, locale, infile, delay)
if infile!=stdin:
infile.close()
exit(retval)

View File

@ -87,7 +87,7 @@ def check_pwd(filename, email, pw_data):
fact_nr = str(mo.group(2)) fact_nr = str(mo.group(2))
fact_pw = str(mo.group(3)) fact_pw = str(mo.group(3))
if pw_data.fac_exists(fact_nr): if pw_data.fac_exists(fact_nr):
if pw_data.check(fact_nr, fact_pw) == 0: if not pw_data.check(fact_nr, fact_pw):
game_email = pw_data.get_email(fact_nr) game_email = pw_data.get_email(fact_nr)
results = results + [ (fact_nr, game_email, False, fact_pw) ] results = results + [ (fact_nr, game_email, False, fact_pw) ]
else: else:

93
process/procmail/rules Normal file
View File

@ -0,0 +1,93 @@
##
## Eressea Reportversand
##
:0:server.lock
* ^Subject:.*ERE.*2.*PASSWOR.*
| sendpassword.py $HOME/eressea/game-2/passwd
:0:server.lock
* ^Subject:.*ERE.*3.*PASSWOR.*
| sendpassword.py $HOME/eressea/game-3/passwd
:0:server.lock
* ^Subject:.*ERE.*4.*PASSWOR.*
| sendpassword.py $HOME/eressea/game-4/passwd
:0:server.lock
* ^Subject:.*ERE.*PASSWOR.*
| sendpassword.py $HOME/eressea/game-2/passwd
:0:server.lock
* ^Subject:.*E3.*PASSWOR.*
| sendpassword.py $HOME/eressea/game-3/passwd
:0:server.lock
* ^Subject:.*ERE.*2.*REPORT \/.*
* !From: .*eressea.*@eressea.de
| tr -d '' | ERESSEA=$HOME/eressea sendreport.sh 2 $FROM $MATCH
:0:server.lock
* ^Subject:.*ERE.*3.*REPORT \/.*
* !From: .*eressea.*@eressea.de
| tr -d '' | ERESSEA=$HOME/eressea sendreport.sh 3 $FROM $MATCH
:0:server.lock
* ^Subject:.*ERE.*4.*REPORT \/.*
* !From: .*eressea.*@eressea.de
| tr -d '' | ERESSEA=$HOME/eressea sendreport.sh 4 $FROM $MATCH
:0:server.lock
* ^Subject:.*ERE.*REPORT \/.*
* !From: .*eressea.*@eressea.de
| sendreport.sh 2 $FROM $MATCH
:0:server.lock
* ^Subject:.*E3.*REPORT \/.*
* !From: .*eressea.*@eressea.de
| tr -d '' | ERESSEA=$HOME/eressea sendreport.sh 3 $FROM $MATCH
:0 he
| ( formail -I"Precedence: junk" -r -A"X-Loop: eressea@eressea.de" ; cat $HOME/etc/report-failure.txt ) | $SENDMAIL -t
##
## Eressea Befehle
##
:0
* ^Subject:.*ERESSEA 4 BEFEHLE
| grep -v '>From' | $HOME/bin/orders-accept 4 de
:0
* ^Subject:.*ERESSEA 4 ORDERS
| grep -v '>From' | $HOME/bin/orders-accept 4 en
:0
* ^Subject:.*ERESSEA 3 BEFEHLE
| grep -v '>From' | $HOME/bin/orders-accept 3 de
:0
* ^Subject:.*ERESSEA 3 ORDERS
| grep -v '>From' | $HOME/bin/orders-accept 3 en
## backwards compatible format
:0
* ^Subject:.*E3.* BEF.*
| grep -v '>From' | $HOME/bin/orders-accept 3 de
:0
* ^Subject:.*E3.* ORD.*
| grep -v '>From' | $HOME/bin/orders-accept 3 en
:0
* ^Subject:.*ERE.* BEF.*
| grep -v '>From' | $HOME/bin/orders-accept 2 de
:0
* ^Subject:.*ERE.* ORD.*
| grep -v '>From' | $HOME/bin/orders-accept 2 en
:0 c
* ^Subject:.*ERE.*
eressea

48
process/sendreport.sh Executable file
View File

@ -0,0 +1,48 @@
#!/bin/bash
## this script takes a backup of a turn.
## usage: backup.sh <turn>
if [ -z $ERESSEA ]; then
echo "You have to define the \$ERESSEA environment variable to run $0"
exit -2
fi
source $HOME/bin/functions.sh
source $HOME/etc/eressea.conf
GAME=$1
EMAIL=$2
FACTION=$3
PASSWD=$4
echo "$GAME $EMAIL $FACTION $PASSWD" >> /tmp/report.log
function reply() {
echo $@ | mutt -s "Reportnachforderung Partei ${FACTION}" $EMAIL
abort $@
}
LOCKFILE=$ERESSEA/.report.lock
[ -e $LOCKFILE ] && reply "lockfile exists. wait for mail delivery to finish."
REPLYTO='accounts@eressea.de'
echo `date`:report:$GAME:$EMAIL:$FACTION:$PASSWD >> $ERESSEA/request.log
cd $ERESSEA
checkpasswd.py game-$GAME/passwd $FACTION $PASSWD || reply "Das Passwort fuer die Partei $FACTION ist ungueltig"
cd $ERESSEA/game-$GAME/reports
if [ ! -e ${FACTION}.sh ]; then
echo "Der Report für Partei $FACTION kann wegen technischer Probleme leider nicht nachgefordert werden: No such file ${FACTION}.sh" \
| mutt -s "Reportnachforderung Partei ${FACTION}" $EMAIL
exit
fi
source ${FACTION}.sh $EMAIL || reply "Unbekannte Partei $FACTION"
if [ -e $ERESSEA/game-$GAME/eressea.db ]; then
SQL="select email from faction f left join faction_data fd on fd.faction_id=f.id where f.game_id=$GAME AND fd.code='$FACTION' and fd.turn=(select max(turn) from faction_data fx where fx.faction_id=f.id)"
OWNER=$(sqlite3 $ERESSEA/game-$GAME/eressea.db "$SQL")
echo "Der Report Deiner Partei wurde an ${EMAIL} gesandt." \
| mutt -s "Reportnachforderung Partei ${FACTION}" $OWNER
fi

View File

@ -20,13 +20,6 @@ BIN_DIR="build-$MACHINE-$CC-Debug"
cd $ROOT/$BIN_DIR cd $ROOT/$BIN_DIR
make install make install
[ -d $DEST/bin ] || mkdir -p $DEST/bin
install -v $ROOT/process/cron/*.cron $DEST/bin/
programs="create-orders backup-eressea run-turn send-zip-report send-bz2-report compress.py compress.sh"
for prg in ${programs} ; do
install -v $ROOT/process/$prg $DEST/bin/
done
# install crontab, but only on the eressea server: # install crontab, but only on the eressea server:
# in fact, never do this, because it overwrites hand-edits # in fact, never do this, because it overwrites hand-edits
#WHOAMI=`whoami`@`hostname` #WHOAMI=`whoami`@`hostname`

View File

@ -108,8 +108,8 @@ local function write_htpasswd()
end end
local function write_files(locales) local function write_files(locales)
write_passwords()
write_htpasswd() write_htpasswd()
-- write_passwords()
write_reports() write_reports()
write_summary() write_summary()
end end

View File

@ -51,7 +51,7 @@ function test_snowglobe()
for k, v in pairs(xform) do for k, v in pairs(xform) do
r2.terrain = k r2.terrain = k
process_orders() process_orders()
assert_equal(v, r2.terrain) -- TODO: re-enable! assert_equal(v, r2.terrain)
if k~=v then if k~=v then
have=have - 1 have=have - 1
else else

View File

@ -58,7 +58,8 @@ attrib_type at_otherfaction = {
struct faction *get_otherfaction(const struct attrib *a) struct faction *get_otherfaction(const struct attrib *a)
{ {
return (faction *)(a->data.v); faction * f = (faction *)(a->data.v);
return (f && f->_alive) ? f : NULL;
} }
struct attrib *make_otherfaction(struct faction *f) struct attrib *make_otherfaction(struct faction *f)

View File

@ -228,21 +228,11 @@ static void message_faction(battle * b, faction * f, struct message *m)
void message_all(battle * b, message * m) void message_all(battle * b, message * m)
{ {
bfaction *bf; bfaction *bf;
plane *p = rplane(b->region);
watcher *w;
for (bf = b->factions; bf; bf = bf->next) { for (bf = b->factions; bf; bf = bf->next) {
assert(bf->faction); assert(bf->faction);
message_faction(b, bf->faction, m); message_faction(b, bf->faction, m);
} }
if (p)
for (w = p->watchers; w; w = w->next) {
for (bf = b->factions; bf; bf = bf->next)
if (bf->faction == w->faction)
break;
if (bf == NULL)
message_faction(b, w->faction, m);
}
} }
static void fbattlerecord(battle * b, faction * f, const char *s) static void fbattlerecord(battle * b, faction * f, const char *s)

View File

@ -384,8 +384,9 @@ static int tolua_faction_create(lua_State * L)
static int tolua_faction_get_password(lua_State * L) static int tolua_faction_get_password(lua_State * L)
{ {
unused_arg(L); faction *self = (faction *)tolua_tousertype(L, 1, 0);
return 0; tolua_pushstring(L, self->_password);
return 1;
} }
static int tolua_faction_set_password(lua_State * L) static int tolua_faction_set_password(lua_State * L)

View File

@ -153,8 +153,8 @@ static int tolua_unit_get_group(lua_State * L)
static int tolua_unit_set_group(lua_State * L) static int tolua_unit_set_group(lua_State * L)
{ {
unit *self = (unit *)tolua_tousertype(L, 1, 0); unit *self = (unit *)tolua_tousertype(L, 1, 0);
int result = join_group(self, tolua_tostring(L, 2, 0)); group *g = join_group(self, tolua_tostring(L, 2, 0));
lua_pushinteger(L, result); lua_pushboolean(L, g!=NULL);
return 1; return 1;
} }

View File

@ -4,6 +4,7 @@
#include "alliance.h" #include "alliance.h"
#include <CuTest.h> #include <CuTest.h>
#include <tests.h> #include <tests.h>
#include <quicklist.h>
#include <assert.h> #include <assert.h>
@ -61,9 +62,31 @@ static void test_alliance_join(CuTest *tc) {
test_cleanup(); test_cleanup();
} }
static void test_alliance_dead_faction(CuTest *tc) {
faction *f, *f2;
alliance *al;
test_cleanup();
f = test_create_faction(0);
f2 = test_create_faction(0);
al = makealliance(42, "Hodor");
setalliance(f, al);
setalliance(f2, al);
CuAssertPtrEquals(tc, f, alliance_get_leader(al));
CuAssertIntEquals(tc, 2, ql_length(al->members));
CuAssertPtrEquals(tc, al, f->alliance);
destroyfaction(&factions);
CuAssertIntEquals(tc, 1, ql_length(al->members));
CuAssertPtrEquals(tc, f2, alliance_get_leader(al));
CuAssertPtrEquals(tc, NULL, f->alliance);
CuAssertTrue(tc, !f->_alive);
test_cleanup();
}
CuSuite *get_alliance_suite(void) CuSuite *get_alliance_suite(void)
{ {
CuSuite *suite = CuSuiteNew(); CuSuite *suite = CuSuiteNew();
SUITE_ADD_TEST(suite, test_alliance_dead_faction);
SUITE_ADD_TEST(suite, test_alliance_make); SUITE_ADD_TEST(suite, test_alliance_make);
SUITE_ADD_TEST(suite, test_alliance_join); SUITE_ADD_TEST(suite, test_alliance_join);
return suite; return suite;

View File

@ -217,6 +217,7 @@ int resolve_faction(variant id, void *address)
result = -1; result = -1;
} }
} }
assert(address);
*(faction **)address = f; *(faction **)address = f;
return result; return result;
} }
@ -333,7 +334,8 @@ variant read_faction_reference(struct storage * store)
void write_faction_reference(const faction * f, struct storage *store) void write_faction_reference(const faction * f, struct storage *store)
{ {
WRITE_INT(store, (f && f->_alive) ? f->no : 0); assert(!f || f->_alive);
WRITE_INT(store, f ? f->no : 0);
} }
static faction *dead_factions; static faction *dead_factions;
@ -566,7 +568,8 @@ void faction_setbanner(faction * self, const char *banner)
void faction_setpassword(faction * f, const char *pwhash) void faction_setpassword(faction * f, const char *pwhash)
{ {
assert(pwhash && pwhash[0] == '$'); assert(pwhash);
// && pwhash[0] == '$');
free(f->_password); free(f->_password);
f->_password = _strdup(pwhash); f->_password = _strdup(pwhash);
} }

View File

@ -179,7 +179,7 @@ void set_group(struct unit *u, struct group *g)
} }
} }
bool join_group(unit * u, const char *name) group *join_group(unit * u, const char *name)
{ {
group *g = NULL; group *g = NULL;
@ -192,7 +192,7 @@ bool join_group(unit * u, const char *name)
} }
set_group(u, g); set_group(u, g);
return true; return g;
} }
void write_groups(struct storage *store, const faction * f) void write_groups(struct storage *store, const faction * f)
@ -203,13 +203,13 @@ void write_groups(struct storage *store, const faction * f)
WRITE_INT(store, g->gid); WRITE_INT(store, g->gid);
WRITE_STR(store, g->name); WRITE_STR(store, g->name);
for (a = g->allies; a; a = a->next) { for (a = g->allies; a; a = a->next) {
if (a->faction) { if (a->faction && a->faction->_alive) {
write_faction_reference(a->faction, store); write_faction_reference(a->faction, store);
WRITE_INT(store, a->status); WRITE_INT(store, a->status);
} }
} }
WRITE_INT(store, 0); write_faction_reference(NULL, store);
write_attribs(store, g->attribs, g); a_write(store, g->attribs, g);
WRITE_SECTION(store); WRITE_SECTION(store);
} }
WRITE_INT(store, 0); WRITE_INT(store, 0);
@ -233,7 +233,7 @@ void read_groups(struct storage *store, faction * f)
ally *a; ally *a;
variant fid; variant fid;
READ_INT(store, &fid.i); fid = read_faction_reference(store);
if (fid.i <= 0) if (fid.i <= 0)
break; break;
a = ally_add(pa, findfaction(fid.i)); a = ally_add(pa, findfaction(fid.i));

View File

@ -36,7 +36,7 @@ extern "C" {
} group; } group;
extern struct attrib_type at_group; /* attribute for units assigned to a group */ extern struct attrib_type at_group; /* attribute for units assigned to a group */
extern bool join_group(struct unit *u, const char *name); extern struct group *join_group(struct unit *u, const char *name);
extern void set_group(struct unit *u, struct group *g); extern void set_group(struct unit *u, struct group *g);
extern struct group * get_group(const struct unit *u); extern struct group * get_group(const struct unit *u);
extern void free_group(struct group *g); extern void free_group(struct group *g);

View File

@ -5,6 +5,8 @@
#include "faction.h" #include "faction.h"
#include "unit.h" #include "unit.h"
#include "region.h" #include "region.h"
#include <util/attrib.h>
#include <attributes/key.h>
#include <stream.h> #include <stream.h>
#include <filestream.h> #include <filestream.h>
#include <storage.h> #include <storage.h>
@ -22,6 +24,7 @@ static void test_group_readwrite(CuTest * tc)
storage store; storage store;
FILE *F; FILE *F;
stream strm; stream strm;
int i;
F = fopen("test.dat", "wb"); F = fopen("test.dat", "wb");
fstream_init(&strm, F); fstream_init(&strm, F);
@ -29,10 +32,13 @@ static void test_group_readwrite(CuTest * tc)
test_cleanup(); test_cleanup();
test_create_world(); test_create_world();
f = test_create_faction(0); f = test_create_faction(0);
g = new_group(f, "test", 42); g = new_group(f, "NW", 42);
g = new_group(f, "Egoisten", 43);
key_set(&g->attribs, 44);
al = ally_add(&g->allies, f); al = ally_add(&g->allies, f);
al->status = HELP_GIVE; al->status = HELP_GIVE;
write_groups(&store, f); write_groups(&store, f);
WRITE_INT(&store, 47);
binstore_done(&store); binstore_done(&store);
fstream_done(&strm); fstream_done(&strm);
@ -40,16 +46,25 @@ static void test_group_readwrite(CuTest * tc)
fstream_init(&strm, F); fstream_init(&strm, F);
binstore_init(&store, &strm); binstore_init(&store, &strm);
f->groups = 0; f->groups = 0;
free_group(g);
read_groups(&store, f); read_groups(&store, f);
READ_INT(&store, &i);
binstore_done(&store); binstore_done(&store);
fstream_done(&strm); fstream_done(&strm);
CuAssertIntEquals(tc, 47, i);
CuAssertPtrNotNull(tc, f->groups); CuAssertPtrNotNull(tc, f->groups);
CuAssertPtrNotNull(tc, f->groups->allies); CuAssertIntEquals(tc, 42, f->groups->gid);
CuAssertPtrEquals(tc, 0, f->groups->allies->next); CuAssertStrEquals(tc, "NW", f->groups->name);
CuAssertPtrEquals(tc, f, f->groups->allies->faction); CuAssertPtrNotNull(tc, f->groups->next);
CuAssertIntEquals(tc, HELP_GIVE, f->groups->allies->status); CuAssertIntEquals(tc, 43, f->groups->next->gid);
CuAssertStrEquals(tc, "Egoisten", f->groups->next->name);
CuAssertPtrEquals(tc, 0, f->groups->allies);
g = f->groups->next;
CuAssertTrue(tc, key_get(g->attribs, 44));
CuAssertPtrNotNull(tc, g->allies);
CuAssertPtrEquals(tc, 0, g->allies->next);
CuAssertPtrEquals(tc, f, g->allies->faction);
CuAssertIntEquals(tc, HELP_GIVE, g->allies->status);
remove("test.dat"); remove("test.dat");
test_cleanup(); test_cleanup();
} }
@ -68,8 +83,8 @@ static void test_group(CuTest * tc)
assert(r && f); assert(r && f);
u = test_create_unit(f, r); u = test_create_unit(f, r);
assert(u); assert(u);
CuAssertTrue(tc, join_group(u, "hodor")); CuAssertPtrNotNull(tc, (g = join_group(u, "hodor")));
CuAssertPtrNotNull(tc, (g = get_group(u))); CuAssertPtrEquals(tc, g, get_group(u));
CuAssertStrEquals(tc, "hodor", g->name); CuAssertStrEquals(tc, "hodor", g->name);
CuAssertIntEquals(tc, 1, g->members); CuAssertIntEquals(tc, 1, g->members);
set_group(u, 0); set_group(u, 0);

View File

@ -290,14 +290,3 @@ int read_plane_reference(plane ** pp, struct storage *store)
ur_add(id, pp, resolve_plane); ur_add(id, pp, resolve_plane);
return AT_READ_OK; return AT_READ_OK;
} }
bool is_watcher(const struct plane * p, const struct faction * f)
{
struct watcher *w;
if (!p)
return false;
w = p->watchers;
while (w && w->faction != f)
w = w->next;
return (w != NULL);
}

View File

@ -22,7 +22,9 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
extern "C" { extern "C" {
#endif #endif
struct faction;
struct region; struct region;
struct faction;
struct plane; struct plane;
struct storage; struct storage;
@ -43,15 +45,8 @@ extern "C" {
#define PFL_NOMONSTERS 16384 /* no monster randenc */ #define PFL_NOMONSTERS 16384 /* no monster randenc */
#define PFL_SEESPECIAL 32768 /* far seeing */ #define PFL_SEESPECIAL 32768 /* far seeing */
typedef struct watcher {
struct watcher *next;
struct faction *faction;
unsigned char mode;
} watcher;
typedef struct plane { typedef struct plane {
struct plane *next; struct plane *next;
struct watcher *watchers;
int id; int id;
char *name; char *name;
int minx, maxx, miny, maxy; int minx, maxx, miny, maxy;
@ -76,7 +71,6 @@ extern "C" {
struct plane *get_homeplane(void); struct plane *get_homeplane(void);
extern int rel_to_abs(const struct plane *pl, const struct faction *f, extern int rel_to_abs(const struct plane *pl, const struct faction *f,
int rel, unsigned char index); int rel, unsigned char index);
extern bool is_watcher(const struct plane *p, const struct faction *f);
extern void write_plane_reference(const plane * p, struct storage *store); extern void write_plane_reference(const plane * p, struct storage *store);
extern int read_plane_reference(plane ** pp, struct storage *store); extern int read_plane_reference(plane ** pp, struct storage *store);
extern int plane_width(const plane * pl); extern int plane_width(const plane * pl);

View File

@ -66,6 +66,7 @@ extern "C" {
struct message_list; struct message_list;
struct rawmaterial; struct rawmaterial;
struct item; struct item;
struct faction;
#define MORALE_TAX_FACTOR 0.005 /* 0.5% tax per point of morale */ #define MORALE_TAX_FACTOR 0.005 /* 0.5% tax per point of morale */
#define MORALE_MAX 10 /* Maximum morale allowed */ #define MORALE_MAX 10 /* Maximum morale allowed */

View File

@ -513,7 +513,8 @@ static void read_owner(struct gamedata *data, region_owner ** powner)
int id; int id;
READ_INT(data->store, &id); READ_INT(data->store, &id);
owner->last_owner = id ? findfaction(id) : NULL; owner->last_owner = id ? findfaction(id) : NULL;
} else if (data->version >= OWNER_2_VERSION) { }
else if (data->version >= OWNER_2_VERSION) {
int id; int id;
alliance *a; alliance *a;
READ_INT(data->store, &id); READ_INT(data->store, &id);
@ -535,11 +536,14 @@ static void read_owner(struct gamedata *data, region_owner ** powner)
static void write_owner(struct gamedata *data, region_owner * owner) static void write_owner(struct gamedata *data, region_owner * owner)
{ {
if (owner) { if (owner) {
faction *f;
WRITE_INT(data->store, owner->since_turn); WRITE_INT(data->store, owner->since_turn);
WRITE_INT(data->store, owner->morale_turn); WRITE_INT(data->store, owner->morale_turn);
WRITE_INT(data->store, owner->flags); WRITE_INT(data->store, owner->flags);
write_faction_reference(owner->last_owner, data->store); f = owner->last_owner;
write_faction_reference(owner->owner, data->store); write_faction_reference((f && f->_alive) ? f : NULL, data->store);
f = owner->owner;
write_faction_reference((f && f->_alive) ? f : NULL, data->store);
} }
else { else {
WRITE_INT(data->store, -1); WRITE_INT(data->store, -1);
@ -778,6 +782,7 @@ void write_unit(struct gamedata *data, const unit * u)
const race *irace = u_irace(u); const race *irace = u_irace(u);
write_unit_reference(u, data->store); write_unit_reference(u, data->store);
assert(u->faction->_alive);
write_faction_reference(u->faction, data->store); write_faction_reference(u->faction, data->store);
WRITE_STR(data->store, u->_name); WRITE_STR(data->store, u->_name);
WRITE_STR(data->store, u->display ? u->display : ""); WRITE_STR(data->store, u->display ? u->display : "");
@ -1171,6 +1176,58 @@ void write_spellbook(const struct spellbook *book, struct storage *store)
WRITE_TOK(store, "end"); WRITE_TOK(store, "end");
} }
static char * getpasswd(int fno) {
const char *prefix = itoa36(fno);
size_t len = strlen(prefix);
FILE * F = fopen("passwords.txt", "r");
char line[80];
if (F) {
while (!feof(F)) {
fgets(line, sizeof(line), F);
if (line[len] == ':' && strncmp(prefix, line, len) == 0) {
size_t slen = strlen(line) - 1;
assert(line[slen] == '\n');
line[slen] = 0;
fclose(F);
return _strdup(line + len + 1);
}
}
fclose(F);
}
return NULL;
}
static void read_password(gamedata *data, faction *f) {
char name[128];
READ_STR(data->store, name, sizeof(name));
if (data->version == BADCRYPT_VERSION) {
char * pass = getpasswd(f->no);
if (pass) {
faction_setpassword(f, password_encode(pass, PASSWORD_DEFAULT));
free(pass); // TODO: remove this allocation!
}
else {
free(f->_password);
f->_password = NULL;
}
}
else {
faction_setpassword(f, (data->version >= CRYPT_VERSION) ? name : password_encode(name, PASSWORD_DEFAULT));
}
}
void _test_read_password(gamedata *data, faction *f) {
read_password(data, f);
}
static void write_password(gamedata *data, const faction *f) {
WRITE_TOK(data->store, (const char *)f->_password);
}
void _test_write_password(gamedata *data, const faction *f) {
write_password(data, f);
}
/** Reads a faction from a file. /** Reads a faction from a file.
* This function requires no context, can be called in any state. The * This function requires no context, can be called in any state. The
* faction may not already exist, however. * faction may not already exist, however.
@ -1238,8 +1295,7 @@ faction *readfaction(struct gamedata * data)
set_email(&f->email, ""); set_email(&f->email, "");
} }
READ_STR(data->store, name, sizeof(name)); read_password(data, f);
faction_setpassword(f, (data->version >= CRYPT_VERSION) ? name : password_encode(name, PASSWORD_DEFAULT));
if (data->version < NOOVERRIDE_VERSION) { if (data->version < NOOVERRIDE_VERSION) {
READ_STR(data->store, 0, 0); READ_STR(data->store, 0, 0);
} }
@ -1326,6 +1382,7 @@ void writefaction(struct gamedata *data, const faction * f)
ally *sf; ally *sf;
ursprung *ur; ursprung *ur;
assert(f->_alive);
write_faction_reference(f, data->store); write_faction_reference(f, data->store);
WRITE_INT(data->store, f->subscription); WRITE_INT(data->store, f->subscription);
#if RELEASE_VERSION >= SPELL_LEVEL_VERSION #if RELEASE_VERSION >= SPELL_LEVEL_VERSION
@ -1346,7 +1403,7 @@ void writefaction(struct gamedata *data, const faction * f)
WRITE_STR(data->store, f->name); WRITE_STR(data->store, f->name);
WRITE_STR(data->store, f->banner); WRITE_STR(data->store, f->banner);
WRITE_STR(data->store, f->email); WRITE_STR(data->store, f->email);
WRITE_TOK(data->store, f->_password); write_password(data, f);
WRITE_TOK(data->store, locale_name(f->locale)); WRITE_TOK(data->store, locale_name(f->locale));
WRITE_INT(data->store, f->lastorders); WRITE_INT(data->store, f->lastorders);
WRITE_INT(data->store, f->age); WRITE_INT(data->store, f->age);
@ -1518,17 +1575,14 @@ int readgame(const char *filename, bool backup)
} }
} }
else { else {
/* WATCHERS - eliminated in February 2016, ca. turn 966 */
if (gdata.version < NOWATCH_VERSION) {
fno = read_faction_reference(&store); fno = read_faction_reference(&store);
while (fno.i) { while (fno.i) {
watcher *w = (watcher *)malloc(sizeof(watcher));
ur_add(fno, &w->faction, resolve_faction);
READ_INT(&store, &n);
w->mode = (unsigned char)n;
w->next = pl->watchers;
pl->watchers = w;
fno = read_faction_reference(&store); fno = read_faction_reference(&store);
} }
} }
}
read_attribs(&store, &pl->attribs, pl); read_attribs(&store, &pl->attribs, pl);
if (pl->id != 1094969858) { // Regatta if (pl->id != 1094969858) { // Regatta
addlist(&planes, pl); addlist(&planes, pl);
@ -1807,7 +1861,6 @@ int writegame(const char *filename)
WRITE_SECTION(&store); WRITE_SECTION(&store);
for (pl = planes; pl; pl = pl->next) { for (pl = planes; pl; pl = pl->next) {
watcher *w;
WRITE_INT(&store, pl->id); WRITE_INT(&store, pl->id);
WRITE_STR(&store, pl->name); WRITE_STR(&store, pl->name);
WRITE_INT(&store, pl->minx); WRITE_INT(&store, pl->minx);
@ -1815,16 +1868,10 @@ int writegame(const char *filename)
WRITE_INT(&store, pl->miny); WRITE_INT(&store, pl->miny);
WRITE_INT(&store, pl->maxy); WRITE_INT(&store, pl->maxy);
WRITE_INT(&store, pl->flags); WRITE_INT(&store, pl->flags);
w = pl->watchers; #if RELEASE_VERSION < NOWATCH_VERSION
while (w) { write_faction_reference(NULL, &store); /* mark the end of pl->watchers (gone since T966) */
if (w->faction) { #endif
write_faction_reference(w->faction, &store); a_write(&store, pl->attribs, pl);
WRITE_INT(&store, w->mode);
}
w = w->next;
}
write_faction_reference(NULL, &store); /* mark the end of the list */
write_attribs(&store, pl->attribs, pl);
WRITE_SECTION(&store); WRITE_SECTION(&store);
} }

View File

@ -86,6 +86,9 @@ extern "C" {
struct gamedata *gamedata_open(const char *filename, const char *mode); struct gamedata *gamedata_open(const char *filename, const char *mode);
void gamedata_close(struct gamedata *data); void gamedata_close(struct gamedata *data);
/* test-only functions that give access to internal implementation details (BAD) */
void _test_write_password(struct gamedata *data, const struct faction *f);
void _test_read_password(struct gamedata *data, struct faction *f);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -5,11 +5,25 @@
#include "save.h" #include "save.h"
#include "unit.h" #include "unit.h"
#include "group.h"
#include "ally.h"
#include "faction.h" #include "faction.h"
#include "plane.h"
#include "region.h"
#include "version.h" #include "version.h"
#include <triggers/changefaction.h>
#include <triggers/createunit.h>
#include <triggers/timeout.h>
#include <util/attrib.h>
#include <util/event.h>
#include <util/base36.h>
#include <util/password.h>
#include <CuTest.h> #include <CuTest.h>
#include <tests.h> #include <tests.h>
#include <storage.h>
#include <stdio.h> #include <stdio.h>
static void test_readwrite_data(CuTest * tc) static void test_readwrite_data(CuTest * tc)
@ -46,6 +60,7 @@ static void test_readwrite_unit(CuTest * tc)
data = gamedata_open(path, "wb"); data = gamedata_open(path, "wb");
CuAssertPtrNotNull(tc, data); // TODO: intermittent test (even after the 'b' fix!) CuAssertPtrNotNull(tc, data); // TODO: intermittent test (even after the 'b' fix!)
write_unit(data, u); write_unit(data, u);
gamedata_close(data); gamedata_close(data);
@ -53,6 +68,8 @@ static void test_readwrite_unit(CuTest * tc)
f = test_create_faction(0); f = test_create_faction(0);
renumber_faction(f, fno); renumber_faction(f, fno);
data = gamedata_open(path, "rb"); data = gamedata_open(path, "rb");
CuAssertPtrNotNull(tc, data);
u = read_unit(data); u = read_unit(data);
gamedata_close(data); gamedata_close(data);
@ -90,11 +107,190 @@ static void test_readwrite_attrib(CuTest *tc) {
test_cleanup(); test_cleanup();
} }
static void test_readwrite_dead_faction_group(CuTest *tc) {
faction *f, *f2;
unit * u;
group *g;
ally *al;
int fno;
test_cleanup();
f = test_create_faction(0);
fno = f->no;
CuAssertPtrEquals(tc, f, factions);
CuAssertPtrEquals(tc, 0, f->next);
f2 = test_create_faction(0);
CuAssertPtrEquals(tc, f2, factions->next);
u = test_create_unit(f2, test_create_region(0, 0, 0));
CuAssertPtrNotNull(tc, u);
g = join_group(u, "group");
CuAssertPtrNotNull(tc, g);
al = ally_add(&g->allies, f);
CuAssertPtrNotNull(tc, al);
CuAssertPtrEquals(tc, f, factions);
destroyfaction(&factions);
CuAssertTrue(tc, !f->_alive);
CuAssertPtrEquals(tc, f2, factions);
writegame("test.dat");
free_gamedata();
f = f2 = NULL;
readgame("test.dat", false);
CuAssertPtrEquals(tc, 0, findfaction(fno));
f2 = factions;
CuAssertPtrNotNull(tc, f2);
u = f2->units;
CuAssertPtrNotNull(tc, u);
g = get_group(u);
CuAssertPtrNotNull(tc, g);
CuAssertPtrEquals(tc, 0, g->allies);
test_cleanup();
}
static void test_readwrite_dead_faction_regionowner(CuTest *tc) {
faction *f;
region *r;
test_cleanup();
config_set("rules.region_owners", "1");
f = test_create_faction(0);
test_create_unit(f, r = test_create_region(0, 0, 0));
region_set_owner(r, f, turn);
destroyfaction(&factions);
CuAssertTrue(tc, !f->_alive);
remove_empty_units();
writegame("test.dat");
free_gamedata();
f = NULL;
readgame("test.dat", false);
f = factions;
CuAssertPtrEquals(tc, 0, f);
r = regions;
CuAssertPtrNotNull(tc, r);
CuAssertPtrEquals(tc, 0, region_get_owner(r));
test_cleanup();
}
static void test_readwrite_dead_faction_changefaction(CuTest *tc) {
faction *f, *f2;
region *r;
trigger *tr;
unit * u;
test_cleanup();
f = test_create_faction(0);
f2 = test_create_faction(0);
u = test_create_unit(f2, r = test_create_region(0, 0, 0));
tr = trigger_changefaction(u, f);
add_trigger(&u->attribs, "timer", trigger_timeout(10, tr));
CuAssertPtrNotNull(tc, a_find(u->attribs, &at_eventhandler));
destroyfaction(&factions);
CuAssertTrue(tc, !f->_alive);
remove_empty_units();
writegame("test.dat");
free_gamedata();
f = NULL;
readgame("test.dat", false);
f = factions;
CuAssertPtrNotNull(tc, f);
r = regions;
CuAssertPtrNotNull(tc, r);
u = r->units;
CuAssertPtrNotNull(tc, u);
CuAssertPtrEquals(tc, 0, a_find(u->attribs, &at_eventhandler));
test_cleanup();
}
static void test_readwrite_dead_faction_createunit(CuTest *tc) {
faction *f, *f2;
region *r;
trigger *tr;
unit * u;
test_cleanup();
f = test_create_faction(0);
f2 = test_create_faction(0);
u = test_create_unit(f2, r = test_create_region(0, 0, 0));
tr = trigger_createunit(r, f, f->race, 1);
add_trigger(&u->attribs, "timer", trigger_timeout(10, tr));
CuAssertPtrNotNull(tc, a_find(u->attribs, &at_eventhandler));
destroyfaction(&factions);
CuAssertTrue(tc, !f->_alive);
remove_empty_units();
writegame("test.dat");
free_gamedata();
f = NULL;
readgame("test.dat", false);
f = factions;
CuAssertPtrNotNull(tc, f);
r = regions;
CuAssertPtrNotNull(tc, r);
u = r->units;
CuAssertPtrNotNull(tc, u);
CuAssertPtrEquals(tc, 0, a_find(u->attribs, &at_eventhandler));
test_cleanup();
}
static void test_read_password(CuTest *tc) {
const char *path = "test.dat";
gamedata *data;
faction *f;
f = test_create_faction(0);
faction_setpassword(f, password_encode("secret", PASSWORD_DEFAULT));
data = gamedata_open(path, "wb");
CuAssertPtrNotNull(tc, data);
_test_write_password(data, f);
gamedata_close(data);
data = gamedata_open(path, "rb");
CuAssertPtrNotNull(tc, data);
_test_read_password(data, f);
gamedata_close(data);
CuAssertTrue(tc, checkpasswd(f, "secret"));
CuAssertIntEquals(tc, 0, remove(path));
}
static void test_read_password_external(CuTest *tc) {
const char *path = "test.dat", *pwfile = "passwords.txt";
gamedata *data;
faction *f;
FILE * F;
remove(pwfile);
f = test_create_faction(0);
faction_setpassword(f, password_encode("secret", PASSWORD_DEFAULT));
CuAssertPtrNotNull(tc, f->_password);
data = gamedata_open(path, "wb");
CuAssertPtrNotNull(tc, data);
WRITE_TOK(data->store, (const char *)f->_password);
WRITE_TOK(data->store, (const char *)f->_password);
gamedata_close(data);
data = gamedata_open(path, "rb");
CuAssertPtrNotNull(tc, data);
data->version = BADCRYPT_VERSION;
_test_read_password(data, f);
CuAssertPtrEquals(tc, 0, f->_password);
F = fopen(pwfile, "wt");
fprintf(F, "%s:secret\n", itoa36(f->no));
fclose(F);
_test_read_password(data, f);
CuAssertPtrNotNull(tc, f->_password);
gamedata_close(data);
CuAssertTrue(tc, checkpasswd(f, "secret"));
CuAssertIntEquals(tc, 0, remove(path));
CuAssertIntEquals(tc, 0, remove(pwfile));
}
CuSuite *get_save_suite(void) CuSuite *get_save_suite(void)
{ {
CuSuite *suite = CuSuiteNew(); CuSuite *suite = CuSuiteNew();
SUITE_ADD_TEST(suite, test_readwrite_attrib); SUITE_ADD_TEST(suite, test_readwrite_attrib);
SUITE_ADD_TEST(suite, test_readwrite_data); SUITE_ADD_TEST(suite, test_readwrite_data);
SUITE_ADD_TEST(suite, test_readwrite_unit); SUITE_ADD_TEST(suite, test_readwrite_unit);
SUITE_ADD_TEST(suite, test_readwrite_dead_faction_createunit);
SUITE_ADD_TEST(suite, test_readwrite_dead_faction_changefaction);
SUITE_ADD_TEST(suite, test_readwrite_dead_faction_regionowner);
DISABLE_TEST(suite, test_readwrite_dead_faction_group);
SUITE_ADD_TEST(suite, test_read_password);
SUITE_ADD_TEST(suite, test_read_password_external);
return suite; return suite;
} }

View File

@ -33,8 +33,10 @@
#define SPELL_LEVEL_VERSION 348 /* f->max_spelllevel gets stored, not calculated */ #define SPELL_LEVEL_VERSION 348 /* f->max_spelllevel gets stored, not calculated */
#define OWNER_3_VERSION 349 /* regions store last owner, not last alliance */ #define OWNER_3_VERSION 349 /* regions store last owner, not last alliance */
#define ATTRIBOWNER_VERSION 350 /* all attrib_type functions know who owns the attribute */ #define ATTRIBOWNER_VERSION 350 /* all attrib_type functions know who owns the attribute */
#define CRYPT_VERSION 351 /* passwords are encrypted */ #define BADCRYPT_VERSION 351 /* passwords are encrypted, poorly */
#define ATHASH_VERSION 352 /* attribute-type hash, not name */ #define ATHASH_VERSION 352 /* attribute-type hash, not name */
#define CRYPT_VERSION 353 /* passwords are encrypted */
#define NOWATCH_VERSION 354 /* plane->watchers is gone */
#define RELEASE_VERSION ATHASH_VERSION /* current datafile */ #define RELEASE_VERSION ATHASH_VERSION /* current datafile */
#define MIN_VERSION INTPAK_VERSION /* minimal datafile we support */ #define MIN_VERSION INTPAK_VERSION /* minimal datafile we support */
#define MAX_VERSION RELEASE_VERSION /* change this if we can need to read the future datafile, and we can do so */ #define MAX_VERSION RELEASE_VERSION /* change this if we can need to read the future datafile, and we can do so */

View File

@ -1385,17 +1385,9 @@ static void prepare_reports(void)
for (r = regions; r; r = r->next) { for (r = regions; r; r = r->next) {
unit *u; unit *u;
plane *p = rplane(r);
reorder_units(r); reorder_units(r);
if (p) {
watcher *w = p->watchers;
for (; w; w = w->next) {
faction_add_seen(w->faction, r, w->mode);
}
}
/* Region owner get always the Lighthouse report */ /* Region owner get always the Lighthouse report */
if (bt_lighthouse && config_token("rules.region_owner_pay_building", bt_lighthouse->_name)) { if (bt_lighthouse && config_token("rules.region_owner_pay_building", bt_lighthouse->_name)) {
for (b = rbuildings(r); b; b = b->next) { for (b = rbuildings(r); b; b = b->next) {
@ -1464,8 +1456,6 @@ static region *lastregion(faction * f)
/* we continue from the best region and look for travelthru etc. */ /* we continue from the best region and look for travelthru etc. */
for (r = f->last->next; r; r = r->next) { for (r = f->last->next; r; r = r->next) {
plane *p = rplane(r);
/* search the region for travelthru-attributes: */ /* search the region for travelthru-attributes: */
if (fval(r, RF_TRAVELUNIT)) { if (fval(r, RF_TRAVELUNIT)) {
travelthru_map(r, cb_set_last, f); travelthru_map(r, cb_set_last, f);
@ -1474,9 +1464,6 @@ static region *lastregion(faction * f)
continue; continue;
if (check_leuchtturm(r, f)) if (check_leuchtturm(r, f))
f->last = r; f->last = r;
if (p && is_watcher(p, f)) {
f->last = r;
}
} }
return f->last->next; return f->last->next;
#else #else

View File

@ -5,6 +5,7 @@
#include "prefix.h" #include "prefix.h"
#include <kernel/config.h> #include <kernel/config.h>
#include <kernel/plane.h>
#include <kernel/region.h> #include <kernel/region.h>
#include <kernel/terrain.h> #include <kernel/terrain.h>
#include <kernel/item.h> #include <kernel/item.h>
@ -47,7 +48,7 @@ struct region *test_create_region(int x, int y, const terrain_type *terrain)
{ {
region *r = findregion(x, y); region *r = findregion(x, y);
if (!r) { if (!r) {
r = new_region(x, y, NULL, 0); r = new_region(x, y, findplane(x, y), 0);
} }
if (!terrain) { if (!terrain) {
terrain_type *t = get_or_create_terrain("plain"); terrain_type *t = get_or_create_terrain("plain");

View File

@ -79,14 +79,20 @@ static void changefaction_write(const trigger * t, struct storage *store)
{ {
changefaction_data *td = (changefaction_data *)t->data.v; changefaction_data *td = (changefaction_data *)t->data.v;
write_unit_reference(td->unit, store); write_unit_reference(td->unit, store);
write_faction_reference(td->faction, store); write_faction_reference(td->faction->_alive ? td->faction : NULL, store);
} }
static int changefaction_read(trigger * t, struct storage *store) static int changefaction_read(trigger * t, struct storage *store)
{ {
variant var;
changefaction_data *td = (changefaction_data *)t->data.v; changefaction_data *td = (changefaction_data *)t->data.v;
read_reference(&td->unit, store, read_unit_reference, resolve_unit); read_reference(&td->unit, store, read_unit_reference, resolve_unit);
read_reference(&td->faction, store, read_faction_reference, resolve_faction); var = read_faction_reference(store);
if (var.i == 0) {
return AT_READ_FAIL;
}
ur_add(var, &td->faction, resolve_faction);
// read_reference(&td->faction, store, read_faction_reference, resolve_faction);
return AT_READ_OK; return AT_READ_OK;
} }

View File

@ -82,7 +82,7 @@ static int createunit_handle(trigger * t, void *data)
static void createunit_write(const trigger * t, struct storage *store) static void createunit_write(const trigger * t, struct storage *store)
{ {
createunit_data *td = (createunit_data *)t->data.v; createunit_data *td = (createunit_data *)t->data.v;
write_faction_reference(td->f, store); write_faction_reference(td->f->_alive ? td->f : NULL, store);
write_region_reference(td->r, store); write_region_reference(td->r, store);
write_race_reference(td->race, store); write_race_reference(td->race, store);
WRITE_INT(store, td->number); WRITE_INT(store, td->number);
@ -91,21 +91,28 @@ static void createunit_write(const trigger * t, struct storage *store)
static int createunit_read(trigger * t, struct storage *store) static int createunit_read(trigger * t, struct storage *store)
{ {
createunit_data *td = (createunit_data *)t->data.v; createunit_data *td = (createunit_data *)t->data.v;
variant var;
int result = AT_READ_OK;
var = read_faction_reference(store);
if (var.i > 0) {
td->f = findfaction(var.i);
if (!td->f) {
ur_add(var, &td->f, resolve_faction);
}
}
else {
result = AT_READ_FAIL;
}
// read_reference(&td->f, store, read_faction_reference, resolve_faction);
int uc =
read_reference(&td->f, store, read_faction_reference, resolve_faction);
int rc =
read_reference(&td->r, store, read_region_reference, read_reference(&td->r, store, read_region_reference,
RESOLVE_REGION(global.data_version)); RESOLVE_REGION(global.data_version));
td->race = (const struct race *)read_race_reference(store).v; td->race = (const struct race *)read_race_reference(store).v;
if (!td->race) {
if (uc == 0 && rc == 0) { result = AT_READ_FAIL;
if (!td->f || !td->r)
return AT_READ_FAIL;
} }
READ_INT(store, &td->number); READ_INT(store, &td->number);
return result;
return AT_READ_OK;
} }
trigger_type tt_createunit = { trigger_type tt_createunit = {

25
src/util/gamedata.test.c Normal file
View File

@ -0,0 +1,25 @@
#include <platform.h>
#include "gamedata.h"
#include <CuTest.h>
#include <tests.h>
#include <stdio.h>
static void test_gamedata(CuTest * tc)
{
gamedata *data;
data = gamedata_open("test.dat", "wb", 0);
CuAssertPtrNotNull(tc, data);
gamedata_close(data);
data = gamedata_open("test.dat", "rb", 0);
CuAssertPtrNotNull(tc, data);
gamedata_close(data);
CuAssertIntEquals(tc, 0, remove("test.dat"));
}
CuSuite *get_gamedata_suite(void)
{
CuSuite *suite = CuSuiteNew();
SUITE_ADD_TEST(suite, test_gamedata);
return suite;
}

View File

@ -3,8 +3,8 @@
#include <md5.h> #include <md5.h>
#include <crypt_blowfish.h> #include <crypt_blowfish.h>
#include <drepper.h>
#include <mtrand.h> #include <mtrand.h>
#include <drepper.h>
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
@ -39,7 +39,7 @@ char *password_gensalt(char *salt, size_t salt_len) {
} }
static bool password_is_implemented(int algo) { static bool password_is_implemented(int algo) {
return algo == PASSWORD_BCRYPT || algo == PASSWORD_PLAIN || algo == PASSWORD_MD5 || algo == PASSWORD_APACHE_MD5; return algo == PASSWORD_PLAINTEXT || algo == PASSWORD_BCRYPT || algo == PASSWORD_NOCRYPT || algo == PASSWORD_MD5 || algo == PASSWORD_APACHE_MD5;
} }
static const char * password_hash_i(const char * passwd, const char *input, int algo, char *result, size_t len) { static const char * password_hash_i(const char * passwd, const char *input, int algo, char *result, size_t len) {
@ -57,7 +57,11 @@ static const char * password_hash_i(const char * passwd, const char *input, int
} }
return result; return result;
} }
else if (algo == PASSWORD_PLAIN) { else if (algo == PASSWORD_PLAINTEXT) {
_snprintf(result, len, "%s", passwd);
return result;
}
else if (algo == PASSWORD_NOCRYPT) {
_snprintf(result, len, "$0$%s", passwd); _snprintf(result, len, "$0$%s", passwd);
return result; return result;
} }
@ -99,17 +103,20 @@ const char * password_encode(const char * passwd, int algo) {
int password_verify(const char * pwhash, const char * passwd) { int password_verify(const char * pwhash, const char * passwd) {
char hash[64]; char hash[64];
int algo; int algo = PASSWORD_PLAINTEXT;
char *pos; char *pos;
const char *result; const char *result;
assert(passwd); assert(passwd);
assert(pwhash); assert(pwhash);
assert(pwhash[0] == '$'); if (pwhash[0] == '$') {
algo = pwhash[1]; algo = pwhash[1];
}
if (!password_is_implemented(algo)) { if (!password_is_implemented(algo)) {
return VERIFY_UNKNOWN; return VERIFY_UNKNOWN;
} }
if (algo == PASSWORD_BCRYPT) { if (algo == PASSWORD_PLAINTEXT) {
return (strcmp(passwd, pwhash) == 0) ? VERIFY_OK : VERIFY_FAIL;
} else if (algo == PASSWORD_BCRYPT) {
char sample[200]; char sample[200];
_crypt_blowfish_rn(passwd, pwhash, sample, sizeof(sample)); _crypt_blowfish_rn(passwd, pwhash, sample, sizeof(sample));
return (strcmp(sample, pwhash) == 0) ? VERIFY_OK : VERIFY_FAIL; return (strcmp(sample, pwhash) == 0) ? VERIFY_OK : VERIFY_FAIL;

View File

@ -1,12 +1,13 @@
#pragma once #pragma once
#define PASSWORD_PLAIN '0' #define PASSWORD_PLAINTEXT 0
#define PASSWORD_NOCRYPT '0'
#define PASSWORD_MD5 '1' #define PASSWORD_MD5 '1'
#define PASSWORD_BCRYPT '2' // not implemented #define PASSWORD_BCRYPT '2' // not implemented
#define PASSWORD_APACHE_MD5 'a' #define PASSWORD_APACHE_MD5 'a'
#define PASSWORD_SHA256 '5' // not implemented #define PASSWORD_SHA256 '5' // not implemented
#define PASSWORD_SHA512 '6' // not implemented #define PASSWORD_SHA512 '6' // not implemented
#define PASSWORD_DEFAULT PASSWORD_APACHE_MD5 #define PASSWORD_DEFAULT PASSWORD_PLAINTEXT
#define VERIFY_OK 0 // password matches hash #define VERIFY_OK 0 // password matches hash
#define VERIFY_FAIL 1 // password is wrong #define VERIFY_FAIL 1 // password is wrong

View File

@ -1,13 +1,10 @@
#include <platform.h> #include <platform.h>
#include <CuTest.h>
#include "password.h" #include "password.h"
#include <CuTest.h>
#include <string.h> #include <string.h>
static void test_passwords(CuTest *tc) { static void test_passwords(CuTest *tc) {
const char *hash, *expect; const char *hash, *expect;
CuAssertIntEquals(tc, VERIFY_UNKNOWN, password_verify("$9$password", "password"));
expect = "$apr1$FqQLkl8g$.icQqaDJpim4BVy.Ho5660"; expect = "$apr1$FqQLkl8g$.icQqaDJpim4BVy.Ho5660";
CuAssertIntEquals(tc, VERIFY_OK, password_verify(expect, "Hodor")); CuAssertIntEquals(tc, VERIFY_OK, password_verify(expect, "Hodor"));
hash = password_encode("Hodor", PASSWORD_APACHE_MD5); hash = password_encode("Hodor", PASSWORD_APACHE_MD5);
@ -20,18 +17,27 @@ static void test_passwords(CuTest *tc) {
CuAssertPtrNotNull(tc, hash); CuAssertPtrNotNull(tc, hash);
CuAssertIntEquals(tc, 0, strncmp(hash, expect, 3)); CuAssertIntEquals(tc, 0, strncmp(hash, expect, 3));
expect = "$0$password"; expect = "password";
hash = password_encode("password", PASSWORD_PLAINTEXT);
CuAssertPtrNotNull(tc, hash);
CuAssertStrEquals(tc, hash, expect);
CuAssertIntEquals(tc, VERIFY_OK, password_verify(expect, "password")); CuAssertIntEquals(tc, VERIFY_OK, password_verify(expect, "password"));
CuAssertIntEquals(tc, VERIFY_FAIL, password_verify(expect, "arseword")); CuAssertIntEquals(tc, VERIFY_FAIL, password_verify(expect, "arseword"));
hash = password_encode("password", PASSWORD_PLAIN);
expect = "$0$password";
hash = password_encode("password", PASSWORD_NOCRYPT);
CuAssertPtrNotNull(tc, hash); CuAssertPtrNotNull(tc, hash);
CuAssertStrEquals(tc, expect, hash); CuAssertStrEquals(tc, hash, expect);
CuAssertIntEquals(tc, VERIFY_OK, password_verify(expect, "password"));
CuAssertIntEquals(tc, VERIFY_FAIL, password_verify(expect, "arseword"));
expect = "$2y$05$RJ8qAhu.foXyJLdc2eHTLOaK4MDYn3/v4HtOVCq0Plv2yxcrEB7Wm"; expect = "$2y$05$RJ8qAhu.foXyJLdc2eHTLOaK4MDYn3/v4HtOVCq0Plv2yxcrEB7Wm";
CuAssertIntEquals(tc, VERIFY_OK, password_verify(expect, "Hodor")); CuAssertIntEquals(tc, VERIFY_OK, password_verify(expect, "Hodor"));
hash = password_encode("Hodor", PASSWORD_BCRYPT); hash = password_encode("Hodor", PASSWORD_BCRYPT);
CuAssertPtrNotNull(tc, hash); CuAssertPtrNotNull(tc, hash);
CuAssertIntEquals(tc, 0, strncmp(hash, expect, 7)); CuAssertIntEquals(tc, 0, strncmp(hash, expect, 7));
CuAssertIntEquals(tc, VERIFY_UNKNOWN, password_verify("$9$saltyfish$password", "password"));
} }
CuSuite *get_password_suite(void) { CuSuite *get_password_suite(void) {

View File

@ -58,6 +58,7 @@ resolve_fun resolver)
void ur_add(variant data, void *ptrptr, resolve_fun fun) void ur_add(variant data, void *ptrptr, resolve_fun fun)
{ {
assert(ptrptr);
if (ur_list == NULL) { if (ur_list == NULL) {
ur_list = malloc(BLOCKSIZE * sizeof(unresolved)); ur_list = malloc(BLOCKSIZE * sizeof(unresolved));
ur_begin = ur_current = ur_list; ur_begin = ur_current = ur_list;
@ -86,6 +87,7 @@ void resolve(void)
ur_list = ur; ur_list = ur;
continue; continue;
} }
assert(ur->ptrptr);
ur->resolve(ur->data, ur->ptrptr); ur->resolve(ur->data, ur->ptrptr);
++ur; ++ur;
} }

Binary file not shown.

View File

@ -1,4 +1,16 @@
ERESSEA 6rLo "4jLm82" ERESSEA 6rLo "6rLo"
EINHEIT 7Lgf EINHEIT 7Lgf
NACH NW NW NACH NW NW
NAECHSTER NAECHSTER
ERESSEA w86y "w86y"
EINHEIT uc3u
STIRB "mrqa"
NAECHSTER
ERESSEA ngij "ngij"
EINHEIT iwbz
HELFE w86y ALLES
EINHEIT j536
GRUPPE "Hodor"
HELFE w86y ALLES
HELFE w86y SILBER NICHT
NAECHSTER

View File

@ -1,4 +1,5 @@
NEWFILES="data/185.dat datum parteien parteien.full htpasswd score turn" NEWFILES="data/185.dat datum parteien parteien.full passwd htpasswd score turn"
cleanup () { cleanup () {
rm -rf reports $NEWFILES rm -rf reports $NEWFILES
} }
@ -38,7 +39,7 @@ SUPP=../share/ubuntu-12_04.supp
SERVER="$VALGRIND --track-origins=yes --gen-suppressions=all --suppressions=$SUPP --error-exitcode=1 --leak-check=no $SERVER" SERVER="$VALGRIND --track-origins=yes --gen-suppressions=all --suppressions=$SUPP --error-exitcode=1 --leak-check=no $SERVER"
fi fi
echo "running $SERVER" echo "running $SERVER"
$SERVER -t 184 ../scripts/run-turn.lua $SERVER -t 184 test-turn.lua
echo "integration tests" echo "integration tests"
[ -d reports ] || quit 4 "no reports directory created" [ -d reports ] || quit 4 "no reports directory created"
CRFILE=185-zvto.cr CRFILE=185-zvto.cr

5
tests/test-turn.lua Normal file
View File

@ -0,0 +1,5 @@
dofile('../scripts/run-turn.lua')
turn = get_turn()
eressea.free_game()
print("trying to read data from " .. turn)
eressea.read_game(turn .. ".dat")

View File

@ -25,7 +25,7 @@ TESTS=../Debug/eressea/test_eressea
SERVER=../Debug/eressea/eressea SERVER=../Debug/eressea/eressea
if [ -n "$VALGRIND" ]; then if [ -n "$VALGRIND" ]; then
SUPP=../share/ubuntu-12_04.supp SUPP=../share/ubuntu-12_04.supp
VALGRIND="$VALGRIND --suppressions=$SUPP --error-exitcode=1 --leak-check=no" VALGRIND="$VALGRIND --track-origins=yes --gen-suppressions=all --suppressions=$SUPP --error-exitcode=1 --leak-check=no"
fi fi
echo "running $TESTS" echo "running $TESTS"
$VALGRIND $TESTS $VALGRIND $TESTS