forked from github/server
commit
136e093c96
20 changed files with 710 additions and 720 deletions
|
@ -9,7 +9,7 @@ end_of_line = lf
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
|
||||||
# 4 space indentation
|
# 4 space indentation
|
||||||
[*.{c,h,lua}]
|
[*.{c,h,lua,py}]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
install(PROGRAMS create-orders backup-eressea run-turn send-zip-report
|
install(PROGRAMS create-orders backup-eressea run-turn send-zip-report
|
||||||
send-bz2-report compress.py compress.sh epasswd.py orders-process
|
send-bz2-report compress.py compress.sh epasswd.py orders-process
|
||||||
|
process-orders.py accept-orders.py
|
||||||
checkpasswd.py sendreport.sh sendreports.sh orders-accept DESTINATION bin)
|
checkpasswd.py sendreport.sh sendreports.sh orders-accept DESTINATION bin)
|
||||||
|
|
||||||
install(DIRECTORY cron/ DESTINATION bin USE_SOURCE_PERMISSIONS
|
install(DIRECTORY cron/ DESTINATION bin USE_SOURCE_PERMISSIONS
|
||||||
|
|
377
process/accept-orders.py
Executable file
377
process/accept-orders.py
Executable file
|
@ -0,0 +1,377 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from email.Utils import parseaddr
|
||||||
|
from email.Parser import Parser
|
||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
import ConfigParser
|
||||||
|
from re import compile, IGNORECASE
|
||||||
|
from stat import ST_MTIME
|
||||||
|
from string import upper, split, replace
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
from sys import stdin
|
||||||
|
from time import ctime, sleep, time
|
||||||
|
from socket import gethostname
|
||||||
|
from rfc822 import parsedate_tz, mktime_tz
|
||||||
|
|
||||||
|
if 'ERESSEA' in os.environ:
|
||||||
|
dir = os.environ['ERESSEA']
|
||||||
|
elif 'HOME' in os.environ:
|
||||||
|
dir = os.path.join(os.environ['HOME'], 'eressea')
|
||||||
|
else: # WTF? No HOME?
|
||||||
|
dir = "/home/eressea/eressea"
|
||||||
|
if not os.path.isdir(dir):
|
||||||
|
print "please set the ERESSEA environment variable to the install path"
|
||||||
|
sys.exit(1)
|
||||||
|
rootdir = dir
|
||||||
|
|
||||||
|
game = int(sys.argv[1])
|
||||||
|
gamedir = os.path.join(rootdir, "game-%d" % (game, ))
|
||||||
|
frommail = 'eressea-server@kn-bremen.de'
|
||||||
|
gamename = 'Eressea'
|
||||||
|
sender = '%s Server <%s>' % (gamename, frommail)
|
||||||
|
|
||||||
|
inifile = os.path.join(gamedir, 'eressea.ini')
|
||||||
|
if not os.path.exists(inifile):
|
||||||
|
print "no such file: " . inifile
|
||||||
|
else:
|
||||||
|
config = ConfigParser.ConfigParser()
|
||||||
|
config.read(inifile)
|
||||||
|
if config.has_option('game', 'email'):
|
||||||
|
frommail = config.get('game', 'email')
|
||||||
|
if config.has_option('game', 'name'):
|
||||||
|
gamename = config.get('game', 'name')
|
||||||
|
if config.has_option('game', 'sender'):
|
||||||
|
sender = config.get('game', 'sender')
|
||||||
|
else:
|
||||||
|
sender = "%s Server <%s>" % (gamename, frommail)
|
||||||
|
config = None
|
||||||
|
prefix = 'turn-'
|
||||||
|
hostname = gethostname()
|
||||||
|
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
|
||||||
|
|
||||||
|
def unlock_file(filename):
|
||||||
|
try:
|
||||||
|
os.unlink(filename+".lock")
|
||||||
|
except:
|
||||||
|
print "could not unlock %s.lock, file not found" % filename
|
||||||
|
|
||||||
|
def lock_file(filename):
|
||||||
|
i = 0
|
||||||
|
wait = 1
|
||||||
|
if not os.path.exists(filename):
|
||||||
|
file=open(filename, "w")
|
||||||
|
file.close()
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
os.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 os.path.exists(filename):
|
||||||
|
maxdate = max(os.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 os.path.exists(dirname): os.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, gamedir, gamename, sender
|
||||||
|
if extend is not None:
|
||||||
|
orderbase = orderbase + ".pre-" + extend
|
||||||
|
savedir = os.path.join(gamedir, orderbase)
|
||||||
|
# check if it's one of the pre-sent orders.
|
||||||
|
# create the save-directories if they don't exist
|
||||||
|
if not os.path.exists(gamedir): os.mkdir(gamedir)
|
||||||
|
if not os.path.exists(savedir): os.mkdir(savedir)
|
||||||
|
# parse message
|
||||||
|
message = Parser().parse(stream)
|
||||||
|
email = get_sender(message)
|
||||||
|
logger = logging.getLogger(email)
|
||||||
|
# write syslog
|
||||||
|
if email is None or valid_email(email)==0:
|
||||||
|
logger.warning("invalid email address: " + str(email))
|
||||||
|
return -1
|
||||||
|
logger.info("received orders from " + email)
|
||||||
|
# get an available filename
|
||||||
|
lock_file(gamedir + "/orders.queue")
|
||||||
|
maxdate, filename = available_file(savedir, prefix + email)
|
||||||
|
if filename is None:
|
||||||
|
logger.warning("more than " + str(maxfiles) + " orders from " + email)
|
||||||
|
return -1
|
||||||
|
# copy the orders to the file
|
||||||
|
text_ok = copy_orders(message, filename, email)
|
||||||
|
unlock_file(gamedir + "/orders.queue")
|
||||||
|
|
||||||
|
warning, msg, fail = None, "", False
|
||||||
|
maildate = message.get("Date")
|
||||||
|
if maildate != None:
|
||||||
|
turndate = mktime_tz(parsedate_tz(maildate))
|
||||||
|
os.utime(filename, (turndate, turndate))
|
||||||
|
logger.debug("mail date is '%s' (%d)" % (maildate, turndate))
|
||||||
|
if False and turndate < maxdate:
|
||||||
|
logger.warning("inconsistent message date " + email)
|
||||||
|
warning = " (" + messages["warning-" + locale] + ")"
|
||||||
|
msg = msg + formatpar(messages["maildate-" + locale] % (ctime(maxdate),ctime(turndate)), 76, 2) + "\n"
|
||||||
|
else:
|
||||||
|
logger.warning("missing message date " + email)
|
||||||
|
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 " + email)
|
||||||
|
os.unlink(filename)
|
||||||
|
savedir = savedir + "/rejected"
|
||||||
|
if not os.path.exists(savedir): os.mkdir(savedir)
|
||||||
|
lock_file(gamedir + "/orders.queue")
|
||||||
|
maxdate, filename = available_file(savedir, prefix + email)
|
||||||
|
store_message(message, filename)
|
||||||
|
unlock_file(gamedir + "/orders.queue")
|
||||||
|
fail = True
|
||||||
|
|
||||||
|
if sendmail and warning is not None:
|
||||||
|
subject = gamename + " " + messages["subject-"+locale] + warning
|
||||||
|
print("mail " + subject)
|
||||||
|
ps = subprocess.Popen(['mutt', '-s', subject, email], stdin=subprocess.PIPE)
|
||||||
|
ps.communicate(msg)
|
||||||
|
|
||||||
|
if not sendmail:
|
||||||
|
print text_ok, fail, email
|
||||||
|
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" % (email, filename, locale, game))
|
||||||
|
queue.close()
|
||||||
|
unlock_file(gamedir + "/orders.queue")
|
||||||
|
|
||||||
|
logger.info("done - accepted orders from " + email)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# the main body of the script:
|
||||||
|
try:
|
||||||
|
os.mkdir(os.path.join(rootdir, 'log'))
|
||||||
|
except:
|
||||||
|
pass # already exists?
|
||||||
|
LOG_FILENAME=os.path.join(rootdir, 'log/orders.log')
|
||||||
|
logging.basicConfig(level=logging.DEBUG, filename=LOG_FILENAME)
|
||||||
|
logger = logging
|
||||||
|
delay = None # TODO: parse the turn delay
|
||||||
|
locale = sys.argv[2]
|
||||||
|
infile = stdin
|
||||||
|
if len(sys.argv)>3:
|
||||||
|
infile = open(sys.argv[3], "r")
|
||||||
|
retval = accept(game, locale, infile, delay)
|
||||||
|
if infile!=stdin:
|
||||||
|
infile.close()
|
||||||
|
sys.exit(retval)
|
|
@ -1,378 +1,5 @@
|
||||||
#!/usr/bin/env python
|
#!/bin/sh
|
||||||
# -*- coding: iso-8859-1 -*-
|
SCRIPT=$(readlink -f $0)
|
||||||
|
cd $(dirname $SCRIPT)
|
||||||
|
python accept-orders.py "$@"
|
||||||
|
|
||||||
from email.Utils import parseaddr
|
|
||||||
from email.Parser import Parser
|
|
||||||
import os
|
|
||||||
import os.path
|
|
||||||
import ConfigParser
|
|
||||||
from re import compile, IGNORECASE
|
|
||||||
from stat import ST_MTIME
|
|
||||||
from string import upper, split, replace
|
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
from sys import stdin
|
|
||||||
from time import ctime, sleep, time
|
|
||||||
from socket import gethostname
|
|
||||||
from rfc822 import parsedate_tz, mktime_tz
|
|
||||||
|
|
||||||
if 'ERESSEA' in os.environ:
|
|
||||||
dir = os.environ['ERESSEA']
|
|
||||||
elif 'HOME' in os.environ:
|
|
||||||
dir = os.path.join(os.environ['HOME'], '/eressea')
|
|
||||||
else: # WTF? No HOME?
|
|
||||||
dir = "/home/eressea/eressea"
|
|
||||||
if not os.path.isdir(dir):
|
|
||||||
print "please set the ERESSEA environment variable to the install path"
|
|
||||||
sys.exit(1)
|
|
||||||
rootdir = dir
|
|
||||||
|
|
||||||
game = int(sys.argv[1])
|
|
||||||
gamedir = os.path.join(rootdir, "game-%d" % (game, ))
|
|
||||||
frommail = 'eressea-server@kn-bremen.de'
|
|
||||||
gamename = 'Eressea'
|
|
||||||
sender = '%s Server <%s>' % (gamename, frommail)
|
|
||||||
|
|
||||||
inifile = os.path.join(gamedir, 'eressea.ini')
|
|
||||||
if not os.path.exists(inifile):
|
|
||||||
print "no such file: " . inifile
|
|
||||||
else:
|
|
||||||
config = ConfigParser.ConfigParser()
|
|
||||||
config.read(inifile)
|
|
||||||
if config.has_option('game', 'email'):
|
|
||||||
frommail = config.get('game', 'email')
|
|
||||||
if config.has_option('game', 'name'):
|
|
||||||
gamename = config.get('game', 'name')
|
|
||||||
if config.has_option('game', 'sender'):
|
|
||||||
sender = config.get('game', 'sender')
|
|
||||||
else:
|
|
||||||
sender = "%s Server <%s>" % (gamename, frommail)
|
|
||||||
config = None
|
|
||||||
prefix = 'turn-'
|
|
||||||
hostname = gethostname()
|
|
||||||
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
|
|
||||||
|
|
||||||
def unlock_file(filename):
|
|
||||||
try:
|
|
||||||
os.unlink(filename+".lock")
|
|
||||||
except:
|
|
||||||
print "could not unlock %s.lock, file not found" % filename
|
|
||||||
|
|
||||||
def lock_file(filename):
|
|
||||||
i = 0
|
|
||||||
wait = 1
|
|
||||||
if not os.path.exists(filename):
|
|
||||||
file=open(filename, "w")
|
|
||||||
file.close()
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
os.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<74>lt keinen " \
|
|
||||||
"Text. Evtl. hast Du den Zug als HTML oder als anderweitig " \
|
|
||||||
"ung<6E>ltig formatierte Mail ingeschickt. Wir k<>nnen ihn " \
|
|
||||||
"deshalb nicht ber<65>cksichtigen. Schicke den Zug nochmals " \
|
|
||||||
"als reinen Text ohne Formatierungen ein.",
|
|
||||||
|
|
||||||
"maildate-de":
|
|
||||||
"Es erreichte uns bereits ein Zug mit einem sp<73>teren " \
|
|
||||||
"Absendedatum (%s > %s). Entweder ist deine " \
|
|
||||||
"Systemzeit verstellt, oder ein Zug hat einen anderen Zug von " \
|
|
||||||
"dir auf dem Transportweg <20>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 os.path.exists(filename):
|
|
||||||
maxdate = max(os.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 os.path.exists(dirname): os.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, gamedir, gamename, sender
|
|
||||||
if extend is not None:
|
|
||||||
orderbase = orderbase + ".pre-" + extend
|
|
||||||
savedir = os.path.join(gamedir, orderbase)
|
|
||||||
# check if it's one of the pre-sent orders.
|
|
||||||
# create the save-directories if they don't exist
|
|
||||||
if not os.path.exists(gamedir): os.mkdir(gamedir)
|
|
||||||
if not os.path.exists(savedir): os.mkdir(savedir)
|
|
||||||
# parse message
|
|
||||||
message = Parser().parse(stream)
|
|
||||||
email = get_sender(message)
|
|
||||||
logger = logging.getLogger(email)
|
|
||||||
# write syslog
|
|
||||||
if email is None or valid_email(email)==0:
|
|
||||||
logger.warning("invalid email address: " + str(email))
|
|
||||||
return -1
|
|
||||||
logger.info("received orders from " + email)
|
|
||||||
# get an available filename
|
|
||||||
lock_file(gamedir + "/orders.queue")
|
|
||||||
maxdate, filename = available_file(savedir, prefix + email)
|
|
||||||
if filename is None:
|
|
||||||
logger.warning("more than " + str(maxfiles) + " orders from " + email)
|
|
||||||
return -1
|
|
||||||
# copy the orders to the file
|
|
||||||
text_ok = copy_orders(message, filename, email)
|
|
||||||
unlock_file(gamedir + "/orders.queue")
|
|
||||||
|
|
||||||
warning, msg, fail = None, "", False
|
|
||||||
maildate = message.get("Date")
|
|
||||||
if maildate != None:
|
|
||||||
turndate = mktime_tz(parsedate_tz(maildate))
|
|
||||||
os.utime(filename, (turndate, turndate))
|
|
||||||
logger.debug("mail date is '%s' (%d)" % (maildate, turndate))
|
|
||||||
if turndate < maxdate:
|
|
||||||
logger.warning("inconsistent message date " + email)
|
|
||||||
warning = " (" + messages["warning-" + locale] + ")"
|
|
||||||
msg = msg + formatpar(messages["maildate-" + locale] % (ctime(maxdate),ctime(turndate)), 76, 2) + "\n"
|
|
||||||
else:
|
|
||||||
logger.warning("missing message date " + email)
|
|
||||||
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 " + email)
|
|
||||||
os.unlink(filename)
|
|
||||||
savedir = savedir + "/rejected"
|
|
||||||
if not os.path.exists(savedir): os.mkdir(savedir)
|
|
||||||
lock_file(gamedir + "/orders.queue")
|
|
||||||
maxdate, filename = available_file(savedir, prefix + email)
|
|
||||||
store_message(message, filename)
|
|
||||||
unlock_file(gamedir + "/orders.queue")
|
|
||||||
fail = True
|
|
||||||
|
|
||||||
if sendmail and warning is not None:
|
|
||||||
subject = gamename + " " + messages["subject-"+locale] + warning
|
|
||||||
mail = "Subject: %s\nFrom: %s\nTo: %s\n\n" % (subject, sender, email) + msg
|
|
||||||
from smtplib import SMTP
|
|
||||||
server = SMTP("localhost")
|
|
||||||
server.sendmail(sender, email, mail)
|
|
||||||
server.close()
|
|
||||||
|
|
||||||
if not sendmail:
|
|
||||||
print text_ok, fail, email
|
|
||||||
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" % (email, filename, locale, game))
|
|
||||||
queue.close()
|
|
||||||
unlock_file(gamedir + "/orders.queue")
|
|
||||||
|
|
||||||
logger.info("done - accepted orders from " + email)
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
# the main body of the script:
|
|
||||||
try:
|
|
||||||
os.mkdir(os.path.join(rootdir, 'log'))
|
|
||||||
except:
|
|
||||||
pass # already exists?
|
|
||||||
LOG_FILENAME=os.path.join(rootdir, 'log/orders.log')
|
|
||||||
logging.basicConfig(level=logging.DEBUG, filename=LOG_FILENAME)
|
|
||||||
logger = logging
|
|
||||||
delay=None # TODO: parse the turn delay
|
|
||||||
locale = sys.argv[2]
|
|
||||||
infile = stdin
|
|
||||||
if len(sys.argv)>3:
|
|
||||||
infile = open(sys.argv[3], "r")
|
|
||||||
retval = accept(game, locale, infile, delay)
|
|
||||||
if infile!=stdin:
|
|
||||||
infile.close()
|
|
||||||
sys.exit(retval)
|
|
||||||
|
|
|
@ -1,242 +1,5 @@
|
||||||
#!/usr/bin/env python
|
#!/bin/sh
|
||||||
# -*- coding: iso-8859-1 -*-
|
SCRIPT=$(readlink -f $0)
|
||||||
|
cd $(dirname $SCRIPT)
|
||||||
|
python process-orders.py "$@"
|
||||||
|
|
||||||
from os import unlink, symlink, rename, popen, tmpfile
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import os.path
|
|
||||||
import ConfigParser
|
|
||||||
from re import compile, IGNORECASE
|
|
||||||
from string import split, join, upper, strip
|
|
||||||
from sys import argv, exit
|
|
||||||
from time import sleep, time, ctime
|
|
||||||
from syslog import openlog, closelog, syslog
|
|
||||||
|
|
||||||
from epasswd import EPasswd
|
|
||||||
|
|
||||||
def pwd_get_email(faction, pwd, pwdfile=None):
|
|
||||||
return None
|
|
||||||
|
|
||||||
def split_filename(filename):
|
|
||||||
return os.path.split(filename)
|
|
||||||
|
|
||||||
def unlock_file(filename):
|
|
||||||
try:
|
|
||||||
unlink(filename+".lock")
|
|
||||||
except:
|
|
||||||
print "could not unlock %s.lock, file not found" % filename
|
|
||||||
raise
|
|
||||||
|
|
||||||
def lock_file(filename):
|
|
||||||
i = 0
|
|
||||||
wait = 1
|
|
||||||
if not os.path.exists(filename):
|
|
||||||
file=open(filename, "w")
|
|
||||||
file.close()
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
symlink(filename, filename+".lock")
|
|
||||||
return
|
|
||||||
except:
|
|
||||||
i = i+1
|
|
||||||
if i == 5:
|
|
||||||
raise
|
|
||||||
sleep(wait)
|
|
||||||
wait = wait*2
|
|
||||||
|
|
||||||
messages = {
|
|
||||||
"subject-de": "Befehle angekommen",
|
|
||||||
"subject-en": "orders received",
|
|
||||||
|
|
||||||
"validate-en": "Validating",
|
|
||||||
"validate-de": "Verarbeite",
|
|
||||||
|
|
||||||
"faction-en": "Faction",
|
|
||||||
"faction-de": "Partei",
|
|
||||||
|
|
||||||
"unknown-de": "WARNUNG: Die Partei ist nicht bekannt, oder das Passwort falsch!",
|
|
||||||
"unknown-en": "WARNING: This faction is unknown, or the password is incorrect!",
|
|
||||||
|
|
||||||
"warning-de": "Warnung",
|
|
||||||
"warning-en": "Warning",
|
|
||||||
|
|
||||||
"error-de": "Fehler",
|
|
||||||
"error-en": "Error",
|
|
||||||
}
|
|
||||||
|
|
||||||
game = int(sys.argv[1])
|
|
||||||
echeck_cmd = "/home/eressea/echeck/echeck.sh"
|
|
||||||
maxlines = 25
|
|
||||||
|
|
||||||
# base directory for all your games:
|
|
||||||
install_dir = "/home/eressea/eressea"
|
|
||||||
if 'ERESSEA' in os.environ:
|
|
||||||
install_dir = os.environ['ERESSEA']
|
|
||||||
elif 'HOME' in os.environ:
|
|
||||||
install_dir = os.path.join(os.environ['HOME'], '/eressea')
|
|
||||||
if not os.path.isdir(install_dir):
|
|
||||||
print "please set the ERESSEA environment variable to the install path"
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
game_dir = os.path.join(install_dir, "game-%d" % (game, ))
|
|
||||||
frommail = 'eressea-server@kn-bremen.de'
|
|
||||||
gamename = 'Eressea'
|
|
||||||
sender = '%s Server <%s>' % (gamename, frommail)
|
|
||||||
|
|
||||||
inifile = os.path.join(game_dir, 'eressea.ini')
|
|
||||||
if not os.path.exists(inifile):
|
|
||||||
print "no such file: " . inifile
|
|
||||||
else:
|
|
||||||
config = ConfigParser.ConfigParser()
|
|
||||||
config.read(inifile)
|
|
||||||
if config.has_option('game', 'email'):
|
|
||||||
frommail = config.get('game', 'email')
|
|
||||||
if config.has_option('game', 'name'):
|
|
||||||
gamename = config.get('game', 'name')
|
|
||||||
if config.has_option('game', 'sender'):
|
|
||||||
sender = config.get('game', 'sender')
|
|
||||||
else:
|
|
||||||
sender = "%s Server <%s>" % (gamename, frommail)
|
|
||||||
config = None
|
|
||||||
|
|
||||||
queue_file = os.path.join(game_dir, "orders.queue")
|
|
||||||
if not os.path.exists(queue_file):
|
|
||||||
exit(0)
|
|
||||||
|
|
||||||
# regular expression that finds the start of a faction
|
|
||||||
fact_re = compile("^\s*(eressea|partei|faction)\s+([a-zA-Z0-9]+)\s+\"?([^\"]*)\"?", IGNORECASE)
|
|
||||||
|
|
||||||
def check_pwd(filename, email, pw_data):
|
|
||||||
results = []
|
|
||||||
try:
|
|
||||||
file = open(filename, "r")
|
|
||||||
except:
|
|
||||||
print "could not open file", filename
|
|
||||||
return results
|
|
||||||
for line in file.readlines():
|
|
||||||
mo = fact_re.search(strip(line))
|
|
||||||
if mo != None:
|
|
||||||
fact_nr = str(mo.group(2))
|
|
||||||
fact_pw = str(mo.group(3))
|
|
||||||
if pw_data.fac_exists(fact_nr):
|
|
||||||
if not pw_data.check(fact_nr, fact_pw):
|
|
||||||
game_email = pw_data.get_email(fact_nr)
|
|
||||||
results = results + [ (fact_nr, game_email, False, fact_pw) ]
|
|
||||||
else:
|
|
||||||
game_email = pw_data.get_email(fact_nr)
|
|
||||||
results = results + [ (fact_nr, game_email, True, fact_pw) ]
|
|
||||||
else:
|
|
||||||
results = results + [ (fact_nr, None, False, fact_pw) ]
|
|
||||||
return results
|
|
||||||
|
|
||||||
def echeck(filename, locale, rules):
|
|
||||||
dirname, filename = split_filename(filename)
|
|
||||||
stream = popen("%s %s %s %s %s" % (echeck_cmd, locale, filename, dirname, rules), 'r')
|
|
||||||
lines = stream.readlines()
|
|
||||||
if len(lines)==0:
|
|
||||||
stream.close()
|
|
||||||
return None
|
|
||||||
if len(lines)>maxlines:
|
|
||||||
mail = join(lines[:maxlines-3] + ["...", "\n"] + lines[-3:], '')
|
|
||||||
else:
|
|
||||||
mail = join(lines[:maxlines], '')
|
|
||||||
stream.close()
|
|
||||||
return mail
|
|
||||||
|
|
||||||
# parse the queue file -
|
|
||||||
#print "connecting to SMTP..."
|
|
||||||
from smtplib import SMTP
|
|
||||||
try:
|
|
||||||
server = SMTP("localhost")
|
|
||||||
except:
|
|
||||||
print "could not connect to SMTP server"
|
|
||||||
exit(0)
|
|
||||||
|
|
||||||
#print "reading password file..."
|
|
||||||
pw_data = EPasswd(os.path.join(game_dir,"passwd"))
|
|
||||||
|
|
||||||
#print "reading orders.queue..."
|
|
||||||
# move the queue file to a save space while locking it:
|
|
||||||
try:
|
|
||||||
lock_file(queue_file)
|
|
||||||
except:
|
|
||||||
exit(0)
|
|
||||||
queuefile = open(queue_file, "r")
|
|
||||||
lines = queuefile.readlines()
|
|
||||||
queuefile.close()
|
|
||||||
|
|
||||||
# copy to a temp file
|
|
||||||
|
|
||||||
tname="/tmp/orders.queue.%s" % str(time())
|
|
||||||
try:
|
|
||||||
lock_file(tname)
|
|
||||||
except:
|
|
||||||
exit(0)
|
|
||||||
tmpfile=open(tname, "w")
|
|
||||||
for line in lines:
|
|
||||||
tmpfile.write(line)
|
|
||||||
tmpfile.close()
|
|
||||||
|
|
||||||
openlog("orders")
|
|
||||||
|
|
||||||
unlink(queue_file)
|
|
||||||
try:
|
|
||||||
unlock_file(queue_file)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
for line in lines:
|
|
||||||
tokens = split(line[:-1], ' ')
|
|
||||||
dict = {}
|
|
||||||
for token in tokens:
|
|
||||||
name, value = split(token, '=')
|
|
||||||
dict[name] = value
|
|
||||||
|
|
||||||
email = dict["email"]
|
|
||||||
locale = dict["locale"]
|
|
||||||
game = int(dict["game"])
|
|
||||||
infile = dict["file"]
|
|
||||||
gamename='[E%d]' % game
|
|
||||||
rules='e%d' % game
|
|
||||||
warning = ""
|
|
||||||
failed = True
|
|
||||||
results = check_pwd(infile, email, pw_data)
|
|
||||||
logfile = open(os.path.join(game_dir, "zug.log"), "a")
|
|
||||||
dirname, filename = split_filename(infile)
|
|
||||||
msg = messages["validate-"+locale] + " " + infile + "\n\n"
|
|
||||||
for faction, game_email, success, pwd in results:
|
|
||||||
msg = msg + messages["faction-"+locale] + " " + faction + "\n"
|
|
||||||
if success: failed = False
|
|
||||||
else: msg = msg + messages["unknown-"+locale] + "\n"
|
|
||||||
msg = msg + "\n"
|
|
||||||
logfile.write("%s:%s:%s:%s:%s:%s\n" % (ctime(time()), email, game_email, faction, pwd, success))
|
|
||||||
logfile.close()
|
|
||||||
|
|
||||||
if failed:
|
|
||||||
warning = " (" + messages["warning-" + locale] + ")"
|
|
||||||
syslog("failed - no valid password in " + infile)
|
|
||||||
else:
|
|
||||||
result = None
|
|
||||||
if os.path.exists(echeck_cmd):
|
|
||||||
result = echeck(infile, locale, rules)
|
|
||||||
if result is None:
|
|
||||||
# echeck did not finish
|
|
||||||
msg = msg + "Echeck is broken. Your turn was accepted, but could not be verified.\n"
|
|
||||||
warning = " (" + messages["warning-" + locale] + ")"
|
|
||||||
syslog("process - echeck broken, " + infile)
|
|
||||||
else:
|
|
||||||
msg = msg + result
|
|
||||||
syslog("process - checked orders in " + infile)
|
|
||||||
|
|
||||||
subject = gamename + " " + messages["subject-" + locale] + warning
|
|
||||||
msg = "Subject: %s\nFrom: %s\nTo: %s\nContent-Type: text/plain; charset=utf-8\n\n" % (subject, sender, email) + msg
|
|
||||||
try:
|
|
||||||
server.sendmail(sender, email, msg)
|
|
||||||
except:
|
|
||||||
syslog("failed - cannot send to " + email)
|
|
||||||
|
|
||||||
server.close()
|
|
||||||
|
|
||||||
closelog()
|
|
||||||
unlink(tname)
|
|
||||||
unlock_file(tname)
|
|
||||||
|
|
215
process/process-orders.py
Executable file
215
process/process-orders.py
Executable file
|
@ -0,0 +1,215 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from os import unlink, symlink, rename, popen, tmpfile
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
import ConfigParser
|
||||||
|
import subprocess
|
||||||
|
from re import compile, IGNORECASE
|
||||||
|
from string import split, join, upper, strip
|
||||||
|
from sys import argv, exit
|
||||||
|
from time import sleep, time, ctime
|
||||||
|
from syslog import openlog, closelog, syslog
|
||||||
|
|
||||||
|
from epasswd import EPasswd
|
||||||
|
|
||||||
|
def pwd_get_email(faction, pwd, pwdfile=None):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def split_filename(filename):
|
||||||
|
return os.path.split(filename)
|
||||||
|
|
||||||
|
def unlock_file(filename):
|
||||||
|
try:
|
||||||
|
unlink(filename+".lock")
|
||||||
|
except:
|
||||||
|
print "could not unlock %s.lock, file not found" % filename
|
||||||
|
raise
|
||||||
|
|
||||||
|
def lock_file(filename):
|
||||||
|
i = 0
|
||||||
|
wait = 1
|
||||||
|
if not os.path.exists(filename):
|
||||||
|
file=open(filename, "w")
|
||||||
|
file.close()
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
symlink(filename, filename+".lock")
|
||||||
|
return
|
||||||
|
except:
|
||||||
|
i = i+1
|
||||||
|
if i == 5:
|
||||||
|
raise
|
||||||
|
sleep(wait)
|
||||||
|
wait = wait*2
|
||||||
|
|
||||||
|
messages = {
|
||||||
|
"subject-de": "Befehle angekommen",
|
||||||
|
"subject-en": "orders received",
|
||||||
|
|
||||||
|
"validate-en": "Validating",
|
||||||
|
"validate-de": "Verarbeite",
|
||||||
|
|
||||||
|
"faction-en": "Faction",
|
||||||
|
"faction-de": "Partei",
|
||||||
|
|
||||||
|
"unknown-de": "WARNUNG: Die Partei ist nicht bekannt, oder das Passwort falsch!",
|
||||||
|
"unknown-en": "WARNING: This faction is unknown, or the password is incorrect!",
|
||||||
|
|
||||||
|
"warning-de": "Warnung",
|
||||||
|
"warning-en": "Warning",
|
||||||
|
|
||||||
|
"error-de": "Fehler",
|
||||||
|
"error-en": "Error",
|
||||||
|
}
|
||||||
|
|
||||||
|
game = int(sys.argv[1])
|
||||||
|
echeck_cmd = "/home/eressea/echeck/echeck.sh"
|
||||||
|
maxlines = 25
|
||||||
|
|
||||||
|
# base directory for all your games:
|
||||||
|
install_dir = "/home/eressea/eressea"
|
||||||
|
if 'ERESSEA' in os.environ:
|
||||||
|
install_dir = os.environ['ERESSEA']
|
||||||
|
elif 'HOME' in os.environ:
|
||||||
|
install_dir = os.path.join(os.environ['HOME'], 'eressea')
|
||||||
|
if not os.path.isdir(install_dir):
|
||||||
|
print "please set the ERESSEA environment variable to the install path"
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
game_dir = os.path.join(install_dir, "game-%d" % (game, ))
|
||||||
|
gamename = 'Eressea'
|
||||||
|
|
||||||
|
inifile = os.path.join(game_dir, 'eressea.ini')
|
||||||
|
queue_file = os.path.join(game_dir, "orders.queue")
|
||||||
|
if not os.path.exists(queue_file):
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
# regular expression that finds the start of a faction
|
||||||
|
fact_re = compile("^\s*(eressea|partei|faction)\s+([a-zA-Z0-9]+)\s+\"?([^\"]*)\"?", IGNORECASE)
|
||||||
|
|
||||||
|
def check_pwd(filename, email, pw_data):
|
||||||
|
results = []
|
||||||
|
try:
|
||||||
|
file = open(filename, "r")
|
||||||
|
except:
|
||||||
|
print "could not open file", filename
|
||||||
|
return results
|
||||||
|
for line in file.readlines():
|
||||||
|
mo = fact_re.search(strip(line))
|
||||||
|
if mo != None:
|
||||||
|
fact_nr = str(mo.group(2))
|
||||||
|
fact_pw = str(mo.group(3))
|
||||||
|
if pw_data.fac_exists(fact_nr):
|
||||||
|
if not pw_data.check(fact_nr, fact_pw):
|
||||||
|
game_email = pw_data.get_email(fact_nr)
|
||||||
|
results = results + [ (fact_nr, game_email, False, fact_pw) ]
|
||||||
|
else:
|
||||||
|
game_email = pw_data.get_email(fact_nr)
|
||||||
|
results = results + [ (fact_nr, game_email, True, fact_pw) ]
|
||||||
|
else:
|
||||||
|
results = results + [ (fact_nr, None, False, fact_pw) ]
|
||||||
|
return results
|
||||||
|
|
||||||
|
def echeck(filename, locale, rules):
|
||||||
|
dirname, filename = split_filename(filename)
|
||||||
|
stream = popen("%s %s %s %s %s" % (echeck_cmd, locale, filename, dirname, rules), 'r')
|
||||||
|
lines = stream.readlines()
|
||||||
|
if len(lines)==0:
|
||||||
|
stream.close()
|
||||||
|
return None
|
||||||
|
if len(lines)>maxlines:
|
||||||
|
mail = join(lines[:maxlines-3] + ["...", "\n"] + lines[-3:], '')
|
||||||
|
else:
|
||||||
|
mail = join(lines[:maxlines], '')
|
||||||
|
stream.close()
|
||||||
|
return mail
|
||||||
|
|
||||||
|
#print "reading password file..."
|
||||||
|
pw_data = EPasswd(os.path.join(game_dir,"passwd"))
|
||||||
|
|
||||||
|
#print "reading orders.queue..."
|
||||||
|
# move the queue file to a save space while locking it:
|
||||||
|
try:
|
||||||
|
lock_file(queue_file)
|
||||||
|
except:
|
||||||
|
exit(0)
|
||||||
|
queuefile = open(queue_file, "r")
|
||||||
|
lines = queuefile.readlines()
|
||||||
|
queuefile.close()
|
||||||
|
|
||||||
|
# copy to a temp file
|
||||||
|
|
||||||
|
tname="/tmp/orders.queue.%s" % str(time())
|
||||||
|
try:
|
||||||
|
lock_file(tname)
|
||||||
|
except:
|
||||||
|
exit(0)
|
||||||
|
tmpfile=open(tname, "w")
|
||||||
|
for line in lines:
|
||||||
|
tmpfile.write(line)
|
||||||
|
tmpfile.close()
|
||||||
|
|
||||||
|
openlog("orders")
|
||||||
|
|
||||||
|
unlink(queue_file)
|
||||||
|
try:
|
||||||
|
unlock_file(queue_file)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
tokens = split(line[:-1], ' ')
|
||||||
|
dict = {}
|
||||||
|
for token in tokens:
|
||||||
|
name, value = split(token, '=')
|
||||||
|
dict[name] = value
|
||||||
|
|
||||||
|
email = dict["email"]
|
||||||
|
locale = dict["locale"]
|
||||||
|
game = int(dict["game"])
|
||||||
|
infile = dict["file"]
|
||||||
|
gamename='[E%d]' % game
|
||||||
|
rules='e%d' % game
|
||||||
|
warning = ""
|
||||||
|
failed = True
|
||||||
|
results = check_pwd(infile, email, pw_data)
|
||||||
|
logfile = open(os.path.join(game_dir, "zug.log"), "a")
|
||||||
|
dirname, filename = split_filename(infile)
|
||||||
|
msg = messages["validate-"+locale] + " " + infile + "\n\n"
|
||||||
|
for faction, game_email, success, pwd in results:
|
||||||
|
msg = msg + messages["faction-"+locale] + " " + faction + "\n"
|
||||||
|
if success: failed = False
|
||||||
|
else: msg = msg + messages["unknown-"+locale] + "\n"
|
||||||
|
msg = msg + "\n"
|
||||||
|
logfile.write("%s:%s:%s:%s:%s:%s\n" % (ctime(time()), email, game_email, faction, pwd, success))
|
||||||
|
logfile.close()
|
||||||
|
|
||||||
|
if failed:
|
||||||
|
warning = " (" + messages["warning-" + locale] + ")"
|
||||||
|
syslog("failed - no valid password in " + infile)
|
||||||
|
else:
|
||||||
|
result = None
|
||||||
|
if os.path.exists(echeck_cmd):
|
||||||
|
result = echeck(infile, locale, rules)
|
||||||
|
if result is None:
|
||||||
|
# echeck did not finish
|
||||||
|
msg = msg + "Echeck is broken. Your turn was accepted, but could not be verified.\n"
|
||||||
|
warning = " (" + messages["warning-" + locale] + ")"
|
||||||
|
syslog("process - echeck broken, " + infile)
|
||||||
|
else:
|
||||||
|
msg = msg + result
|
||||||
|
syslog("process - checked orders in " + infile)
|
||||||
|
|
||||||
|
subject = gamename + " " + messages["subject-" + locale] + warning
|
||||||
|
try:
|
||||||
|
sp = subprocess.Popen(['mutt', '-s', subject, email], stdin=subprocess.PIPE)
|
||||||
|
sp.communicate(msg)
|
||||||
|
except:
|
||||||
|
syslog("failed - cannot send to " + email)
|
||||||
|
|
||||||
|
closelog()
|
||||||
|
unlink(tname)
|
||||||
|
unlock_file(tname)
|
|
@ -698,10 +698,16 @@
|
||||||
<arg name="faction" type="faction"/>
|
<arg name="faction" type="faction"/>
|
||||||
</type>
|
</type>
|
||||||
</message>
|
</message>
|
||||||
<message name="nr_borderlist_postfix" section="nr">
|
<message name="nr_border_transparent" section="nr">
|
||||||
<type>
|
<type>
|
||||||
<arg name="transparent" type="int"/>
|
|
||||||
<arg name="object" type="string"/>
|
<arg name="object" type="string"/>
|
||||||
|
<arg name="dir" type="direction"/>
|
||||||
|
</type>
|
||||||
|
</message>
|
||||||
|
<message name="nr_border_opaque" section="nr">
|
||||||
|
<type>
|
||||||
|
<arg name="object" type="string"/>
|
||||||
|
<arg name="dir" type="direction"/>
|
||||||
</type>
|
</type>
|
||||||
</message>
|
</message>
|
||||||
<message name="nr_building_besieged" section="nr">
|
<message name="nr_building_besieged" section="nr">
|
||||||
|
@ -5494,12 +5500,12 @@
|
||||||
<arg name="unit" type="unit"/>
|
<arg name="unit" type="unit"/>
|
||||||
</type>
|
</type>
|
||||||
</message>
|
</message>
|
||||||
<message name="tactics_lost" section="battle">
|
<message name="para_tactics_lost" section="battle">
|
||||||
<type>
|
<type>
|
||||||
<arg name="unit" type="unit"/>
|
<arg name="unit" type="unit"/>
|
||||||
</type>
|
</type>
|
||||||
</message>
|
</message>
|
||||||
<message name="tactics_won" section="battle">
|
<message name="para_tactics_won" section="battle">
|
||||||
<type>
|
<type>
|
||||||
<arg name="unit" type="unit"/>
|
<arg name="unit" type="unit"/>
|
||||||
</type>
|
</type>
|
||||||
|
|
|
@ -1355,8 +1355,11 @@ msgstr "\"Es herrscht eine fröhliche und ausgelassene Stimmung. ($int36($id))\"
|
||||||
msgid "buildroad"
|
msgid "buildroad"
|
||||||
msgstr "\"$unit($unit) erweitert in $region($region) das Straßennetz um $int($size).\""
|
msgstr "\"$unit($unit) erweitert in $region($region) das Straßennetz um $int($size).\""
|
||||||
|
|
||||||
msgid "nr_borderlist_postfix"
|
msgid "nr_border_opaque"
|
||||||
msgstr "\"$if($transparent,\" befindet sich\",\" versperrt\") ${object}$if($transparent,\"\",\" die Sicht\").\""
|
msgstr "Im $direction($dir) verperrt ${object} die Sicht."
|
||||||
|
|
||||||
|
msgid "nr_border_transparent"
|
||||||
|
msgstr "Im $direction($dir) befindet sich ${object}."
|
||||||
|
|
||||||
msgid "effectstrength"
|
msgid "effectstrength"
|
||||||
msgstr "\"$unit($mage) erhöht die Körperkraft von $unit.dative($target) beträchtlich.\""
|
msgstr "\"$unit($mage) erhöht die Körperkraft von $unit.dative($target) beträchtlich.\""
|
||||||
|
|
|
@ -1355,8 +1355,11 @@ msgstr "\"Everyone in this region seems to be having a very good time. ($int36($
|
||||||
msgid "buildroad"
|
msgid "buildroad"
|
||||||
msgstr "\"$unit($unit) extends the road network in $region($region) by $int($size).\""
|
msgstr "\"$unit($unit) extends the road network in $region($region) by $int($size).\""
|
||||||
|
|
||||||
msgid "nr_borderlist_postfix"
|
msgid "nr_border_opaque"
|
||||||
msgstr "\"$if($transparent,\" there is\",\" sight is blocked by \") ${object}.\""
|
msgstr "To the $direction($dir), ${object} is blocking the view."
|
||||||
|
|
||||||
|
msgid "nr_border_transparent"
|
||||||
|
msgstr "To the $direction($dir) is ${object}."
|
||||||
|
|
||||||
msgid "effectstrength"
|
msgid "effectstrength"
|
||||||
msgstr "\"$unit($mage) increases the strength of $unit($target) dramatically.\""
|
msgstr "\"$unit($mage) increases the strength of $unit($target) dramatically.\""
|
||||||
|
|
|
@ -1906,9 +1906,6 @@ msgstr "Mallorn"
|
||||||
msgid "castle"
|
msgid "castle"
|
||||||
msgstr "Burg"
|
msgstr "Burg"
|
||||||
|
|
||||||
msgid "nr_borderlist_infix"
|
|
||||||
msgstr ", im"
|
|
||||||
|
|
||||||
msgctxt "race"
|
msgctxt "race"
|
||||||
msgid "shadowbat_p"
|
msgid "shadowbat_p"
|
||||||
msgstr "Todesflattern"
|
msgstr "Todesflattern"
|
||||||
|
@ -3706,9 +3703,6 @@ msgctxt "spell"
|
||||||
msgid "analyse_object"
|
msgid "analyse_object"
|
||||||
msgstr "Lied des Ortes analysieren"
|
msgstr "Lied des Ortes analysieren"
|
||||||
|
|
||||||
msgid "nr_borderlist_lastfix"
|
|
||||||
msgstr "und im"
|
|
||||||
|
|
||||||
msgctxt "race"
|
msgctxt "race"
|
||||||
msgid "shadowknight_d"
|
msgid "shadowknight_d"
|
||||||
msgstr "Schattenrittern"
|
msgstr "Schattenrittern"
|
||||||
|
@ -5573,7 +5567,7 @@ msgid "swamp_trail"
|
||||||
msgstr "der Sumpf von %s"
|
msgstr "der Sumpf von %s"
|
||||||
|
|
||||||
msgid "nr_nb_final"
|
msgid "nr_nb_final"
|
||||||
msgstr "und im"
|
msgstr "und im "
|
||||||
|
|
||||||
msgid "aoc"
|
msgid "aoc"
|
||||||
msgstr "Katzenamulett"
|
msgstr "Katzenamulett"
|
||||||
|
|
|
@ -1642,9 +1642,6 @@ msgstr "mallorn"
|
||||||
msgid "castle"
|
msgid "castle"
|
||||||
msgstr "castle"
|
msgstr "castle"
|
||||||
|
|
||||||
msgid "nr_borderlist_infix"
|
|
||||||
msgstr ", to the "
|
|
||||||
|
|
||||||
msgctxt "race"
|
msgctxt "race"
|
||||||
msgid "shadowbat_p"
|
msgid "shadowbat_p"
|
||||||
msgstr "darkbats"
|
msgstr "darkbats"
|
||||||
|
@ -3261,9 +3258,6 @@ msgctxt "spell"
|
||||||
msgid "analyse_object"
|
msgid "analyse_object"
|
||||||
msgstr "Analysis"
|
msgstr "Analysis"
|
||||||
|
|
||||||
msgid "nr_borderlist_lastfix"
|
|
||||||
msgstr ", and to the "
|
|
||||||
|
|
||||||
msgctxt "race"
|
msgctxt "race"
|
||||||
msgid "shadowknight_d"
|
msgid "shadowknight_d"
|
||||||
msgstr "shadow knights"
|
msgstr "shadow knights"
|
||||||
|
|
|
@ -491,8 +491,29 @@ function test_dwarf_mining()
|
||||||
local f = faction.create('dwarf')
|
local f = faction.create('dwarf')
|
||||||
local r = region.create(0, 0, 'plain')
|
local r = region.create(0, 0, 'plain')
|
||||||
local u = unit.create(f, r)
|
local u = unit.create(f, r)
|
||||||
u.name = 'Xolgrim'
|
|
||||||
u:set_skill('mining', 2)
|
u:set_skill('mining', 2)
|
||||||
assert_equal(2, u:get_skill('mining'))
|
assert_equal(2, u:get_skill('mining'))
|
||||||
assert_equal(4, u:eff_skill('mining'))
|
assert_equal(4, u:eff_skill('mining'))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function test_buy_sell()
|
||||||
|
local f = faction.create('human')
|
||||||
|
local r = region.create(0, 0, 'plain')
|
||||||
|
local u = unit.create(f, r)
|
||||||
|
local lux = r.luxury
|
||||||
|
local b = building.create(r, 'castle')
|
||||||
|
b.size = 10
|
||||||
|
u:set_skill('trade', 1)
|
||||||
|
item = 'silk'
|
||||||
|
name = 'Seide'
|
||||||
|
if lux == 'silk' then
|
||||||
|
item = 'balm'
|
||||||
|
name = 'Balsam'
|
||||||
|
end
|
||||||
|
u:add_item(item, 5)
|
||||||
|
u:add_order('VERKAUFE 1 ' .. name)
|
||||||
|
assert_equal(0, u:get_item('money'))
|
||||||
|
process_orders()
|
||||||
|
assert_equal(4, u:get_item(item))
|
||||||
|
assert_not_equal(0, u:get_item('money'))
|
||||||
|
end
|
||||||
|
|
|
@ -818,7 +818,9 @@ static void start_resources(parseinfo *pi, const XML_Char *el, const XML_Char **
|
||||||
handle_requirement(pi, el, attr);
|
handle_requirement(pi, el, attr);
|
||||||
}
|
}
|
||||||
else if (xml_strcmp(el, "luxury") == 0) {
|
else if (xml_strcmp(el, "luxury") == 0) {
|
||||||
rtype->ltype = new_luxurytype(itype, 0);
|
int price = atoi(attr_get(attr, "price"));
|
||||||
|
assert(price > 0);
|
||||||
|
rtype->ltype = new_luxurytype(itype, price);
|
||||||
}
|
}
|
||||||
else if (xml_strcmp(el, "potion") == 0) {
|
else if (xml_strcmp(el, "potion") == 0) {
|
||||||
int i, level = 0;
|
int i, level = 0;
|
||||||
|
@ -1090,9 +1092,13 @@ static void start_races(parseinfo *pi, const XML_Char *el, const XML_Char **attr
|
||||||
|
|
||||||
name = attr_get(attr, "name");
|
name = attr_get(attr, "name");
|
||||||
if (name) {
|
if (name) {
|
||||||
|
int i;
|
||||||
|
|
||||||
assert(!rc);
|
assert(!rc);
|
||||||
pi->object = rc = rc_get_or_create(name);
|
pi->object = rc = rc_get_or_create(name);
|
||||||
int i;
|
while (AT_NONE != rc->attack[nattacks].type) {
|
||||||
|
++nattacks;
|
||||||
|
}
|
||||||
|
|
||||||
for (i = 0; attr[i]; i += 2) {
|
for (i = 0; attr[i]; i += 2) {
|
||||||
const XML_Char *key = attr[i], *val = attr[i + 1];
|
const XML_Char *key = attr[i], *val = attr[i + 1];
|
||||||
|
|
|
@ -188,7 +188,7 @@ border_type *find_bordertype(const char *name)
|
||||||
{
|
{
|
||||||
border_type *bt = bordertypes;
|
border_type *bt = bordertypes;
|
||||||
|
|
||||||
while (bt && strcmp(bt->__name, name)!=0)
|
while (bt && strcmp(bt->_name, name)!=0)
|
||||||
bt = bt->next;
|
bt = bt->next;
|
||||||
return bt;
|
return bt;
|
||||||
}
|
}
|
||||||
|
@ -563,7 +563,7 @@ void write_borders(struct storage *store)
|
||||||
for (b = bhash; b != NULL; b = b->next) {
|
for (b = bhash; b != NULL; b = b->next) {
|
||||||
if (b->type->valid && !b->type->valid(b))
|
if (b->type->valid && !b->type->valid(b))
|
||||||
continue;
|
continue;
|
||||||
WRITE_TOK(store, b->type->__name);
|
WRITE_TOK(store, b->type->_name);
|
||||||
WRITE_INT(store, b->id);
|
WRITE_INT(store, b->id);
|
||||||
WRITE_INT(store, b->from->uid);
|
WRITE_INT(store, b->from->uid);
|
||||||
WRITE_INT(store, b->to->uid);
|
WRITE_INT(store, b->to->uid);
|
||||||
|
@ -614,7 +614,7 @@ int read_borders(gamedata *data)
|
||||||
if (to == from && from) {
|
if (to == from && from) {
|
||||||
direction_t dir = (direction_t)(rng_int() % MAXDIRECTIONS);
|
direction_t dir = (direction_t)(rng_int() % MAXDIRECTIONS);
|
||||||
region *r = rconnect(from, dir);
|
region *r = rconnect(from, dir);
|
||||||
log_error("[read_borders] invalid %s in %s\n", type->__name, regionname(from, NULL));
|
log_error("[read_borders] invalid %s in %s\n", type->_name, regionname(from, NULL));
|
||||||
if (r != NULL)
|
if (r != NULL)
|
||||||
to = r;
|
to = r;
|
||||||
}
|
}
|
||||||
|
@ -633,5 +633,5 @@ int read_borders(gamedata *data)
|
||||||
}
|
}
|
||||||
|
|
||||||
const char * border_name(const connection *co, const struct region * r, const struct faction * f, int flags) {
|
const char * border_name(const connection *co, const struct region * r, const struct faction * f, int flags) {
|
||||||
return (co->type->name) ? co->type->name(co, r, f, flags) : co->type->__name;
|
return (co->type->name) ? co->type->name(co, r, f, flags) : co->type->_name;
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ extern "C" {
|
||||||
} connection;
|
} connection;
|
||||||
|
|
||||||
typedef struct border_type {
|
typedef struct border_type {
|
||||||
const char *__name; /* internal use only */
|
const char *_name; /* internal use only */
|
||||||
variant_type datatype;
|
variant_type datatype;
|
||||||
bool(*transparent) (const connection *, const struct faction *);
|
bool(*transparent) (const connection *, const struct faction *);
|
||||||
/* is it possible to see through this? */
|
/* is it possible to see through this? */
|
||||||
|
|
74
src/move.c
74
src/move.c
|
@ -73,7 +73,6 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
#include <util/assert.h>
|
#include <util/assert.h>
|
||||||
#include <util/attrib.h>
|
#include <util/attrib.h>
|
||||||
#include <util/base36.h>
|
#include <util/base36.h>
|
||||||
#include <util/bsdstring.h>
|
|
||||||
#include <util/gamedata.h>
|
#include <util/gamedata.h>
|
||||||
#include <util/language.h>
|
#include <util/language.h>
|
||||||
#include <util/lists.h>
|
#include <util/lists.h>
|
||||||
|
@ -1047,15 +1046,17 @@ int movewhere(const unit * u, const char *token, region * r, region ** resultp)
|
||||||
order * cycle_route(order * ord, const struct locale *lang, int gereist)
|
order * cycle_route(order * ord, const struct locale *lang, int gereist)
|
||||||
{
|
{
|
||||||
int cm = 0;
|
int cm = 0;
|
||||||
char tail[1024], *bufp = tail;
|
char tail[1024];
|
||||||
char neworder[2048], *obuf = neworder;
|
char neworder[2048];
|
||||||
char token[128];
|
char token[128];
|
||||||
direction_t d = NODIRECTION;
|
direction_t d = NODIRECTION;
|
||||||
bool paused = false;
|
bool paused = false;
|
||||||
bool pause;
|
|
||||||
order *norder;
|
order *norder;
|
||||||
size_t size = sizeof(tail) - 1;
|
sbstring sbtail;
|
||||||
|
sbstring sborder;
|
||||||
|
|
||||||
|
sbs_init(&sbtail, tail, sizeof(tail));
|
||||||
|
sbs_init(&sborder, neworder, sizeof(neworder));
|
||||||
assert(getkeyword(ord) == K_ROUTE);
|
assert(getkeyword(ord) == K_ROUTE);
|
||||||
tail[0] = '\0';
|
tail[0] = '\0';
|
||||||
neworder[0] = '\0';
|
neworder[0] = '\0';
|
||||||
|
@ -1063,14 +1064,10 @@ order * cycle_route(order * ord, const struct locale *lang, int gereist)
|
||||||
|
|
||||||
for (cm = 0;; ++cm) {
|
for (cm = 0;; ++cm) {
|
||||||
const char *s;
|
const char *s;
|
||||||
pause = false;
|
|
||||||
s = gettoken(token, sizeof(token));
|
s = gettoken(token, sizeof(token));
|
||||||
if (s && *s) {
|
if (s && *s) {
|
||||||
d = get_direction(s, lang);
|
d = get_direction(s, lang);
|
||||||
if (d == D_PAUSE) {
|
if (d == NODIRECTION) {
|
||||||
pause = true;
|
|
||||||
}
|
|
||||||
else if (d == NODIRECTION) {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1079,38 +1076,37 @@ order * cycle_route(order * ord, const struct locale *lang, int gereist)
|
||||||
}
|
}
|
||||||
if (cm < gereist) {
|
if (cm < gereist) {
|
||||||
/* TODO: hier sollte keine PAUSE auftreten */
|
/* TODO: hier sollte keine PAUSE auftreten */
|
||||||
assert(!pause);
|
assert (d != D_PAUSE);
|
||||||
if (!pause) {
|
if (d != D_PAUSE) {
|
||||||
const char *loc = LOC(lang, shortdirections[d]);
|
const char *loc = LOC(lang, shortdirections[d]);
|
||||||
assert(loc);
|
assert(loc);
|
||||||
if (bufp != tail) {
|
if (sbs_length(&sbtail) > 0) {
|
||||||
bufp = STRLCPY_EX(bufp, " ", &size, "cycle_route");
|
sbs_strcat(&sbtail, " ");
|
||||||
}
|
}
|
||||||
bufp = STRLCPY_EX(bufp, loc, &size, "cycle_route");
|
sbs_strcat(&sbtail, loc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (strlen(neworder) > sizeof(neworder) / 2)
|
else if (strlen(neworder) > sizeof(neworder) / 2)
|
||||||
break;
|
break;
|
||||||
else if (cm == gereist && !paused && pause) {
|
else if (cm == gereist && !paused && (d == D_PAUSE)) {
|
||||||
const char *loc = LOC(lang, parameters[P_PAUSE]);
|
const char *loc = LOC(lang, parameters[P_PAUSE]);
|
||||||
bufp = STRLCPY_EX(bufp, " ", &size, "cycle_route");
|
sbs_strcat(&sbtail, " ");
|
||||||
bufp = STRLCPY_EX(bufp, loc, &size, "cycle_route");
|
sbs_strcat(&sbtail, loc);
|
||||||
paused = true;
|
paused = true;
|
||||||
}
|
}
|
||||||
else if (pause) {
|
|
||||||
/* da PAUSE nicht in ein shortdirections[d] umgesetzt wird (ist
|
|
||||||
* hier keine normale direction), muss jede PAUSE einzeln
|
|
||||||
* herausgefiltert und explizit gesetzt werden */
|
|
||||||
if (neworder != obuf) {
|
|
||||||
obuf += str_strlcat(obuf, " ", sizeof(neworder) - (obuf - neworder));
|
|
||||||
}
|
|
||||||
obuf += str_strlcat(obuf, LOC(lang, parameters[P_PAUSE]), sizeof(neworder) - (obuf - neworder));
|
|
||||||
}
|
|
||||||
else {
|
else {
|
||||||
if (neworder != obuf) {
|
if (sbs_length(&sbtail) > 0) {
|
||||||
obuf += str_strlcat(obuf, " ", sizeof(neworder) - (obuf - neworder));
|
sbs_strcat(&sborder, " ");
|
||||||
|
}
|
||||||
|
if (d == D_PAUSE) {
|
||||||
|
/* da PAUSE nicht in ein shortdirections[d] umgesetzt wird (ist
|
||||||
|
* hier keine normale direction), muss jede PAUSE einzeln
|
||||||
|
* herausgefiltert und explizit gesetzt werden */
|
||||||
|
sbs_strcat(&sborder, LOC(lang, parameters[P_PAUSE]));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sbs_strcat(&sborder, LOC(lang, shortdirections[d]));
|
||||||
}
|
}
|
||||||
obuf += str_strlcat(obuf, LOC(lang, shortdirections[d]), sizeof(neworder) - (obuf - neworder));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2250,10 +2246,9 @@ static direction_t hunted_dir(attrib * at, int id)
|
||||||
int follow_ship(unit * u, order * ord)
|
int follow_ship(unit * u, order * ord)
|
||||||
{
|
{
|
||||||
region *rc = u->region;
|
region *rc = u->region;
|
||||||
size_t bytes;
|
sbstring sbcmd;
|
||||||
int moves, id, speed;
|
int moves, id, speed;
|
||||||
char command[256], *bufp = command;
|
char command[256];
|
||||||
size_t size = sizeof(command);
|
|
||||||
direction_t dir;
|
direction_t dir;
|
||||||
|
|
||||||
if (fval(u, UFL_NOTMOVING)) {
|
if (fval(u, UFL_NOTMOVING)) {
|
||||||
|
@ -2289,11 +2284,10 @@ int follow_ship(unit * u, order * ord)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bufp = command;
|
sbs_init(&sbcmd, command, sizeof(command));
|
||||||
bytes = slprintf(bufp, size, "%s %s", LOC(u->faction->locale, keyword(K_MOVE)), LOC(u->faction->locale, directions[dir]));
|
sbs_strcpy(&sbcmd, LOC(u->faction->locale, keyword(K_MOVE)));
|
||||||
assert(bytes <= INT_MAX);
|
sbs_strcat(&sbcmd, " ");
|
||||||
if (wrptr(&bufp, &size, (int)bytes) != 0)
|
sbs_strcat(&sbcmd, LOC(u->faction->locale, directions[dir]));
|
||||||
WARN_STATIC_BUFFER();
|
|
||||||
|
|
||||||
moves = 1;
|
moves = 1;
|
||||||
|
|
||||||
|
@ -2309,8 +2303,8 @@ int follow_ship(unit * u, order * ord)
|
||||||
rc = rconnect(rc, dir);
|
rc = rconnect(rc, dir);
|
||||||
while (rc && moves < speed && (dir = hunted_dir(rc->attribs, id)) != NODIRECTION) {
|
while (rc && moves < speed && (dir = hunted_dir(rc->attribs, id)) != NODIRECTION) {
|
||||||
const char *loc = LOC(u->faction->locale, directions[dir]);
|
const char *loc = LOC(u->faction->locale, directions[dir]);
|
||||||
bufp = STRLCPY_EX(bufp, " ", &size, "hunt");
|
sbs_strcat(&sbcmd, " ");
|
||||||
bufp = STRLCPY_EX(bufp, loc, &size, "hunt");
|
sbs_strcat(&sbcmd, loc);
|
||||||
moves++;
|
moves++;
|
||||||
rc = rconnect(rc, dir);
|
rc = rconnect(rc, dir);
|
||||||
}
|
}
|
||||||
|
|
|
@ -495,6 +495,7 @@ static void test_follow_ship_msg(CuTest * tc) {
|
||||||
|
|
||||||
follow_ship(u, ord);
|
follow_ship(u, ord);
|
||||||
|
|
||||||
|
CuAssertPtrEquals(tc, r, u->region);
|
||||||
CuAssertPtrNotNull(tc, msg = test_find_messagetype(u->faction->msgs, "error18"));
|
CuAssertPtrNotNull(tc, msg = test_find_messagetype(u->faction->msgs, "error18"));
|
||||||
p = msg->parameters[2].v;
|
p = msg->parameters[2].v;
|
||||||
CuAssertPtrNotNull(tc, p);
|
CuAssertPtrNotNull(tc, p);
|
||||||
|
|
43
src/report.c
43
src/report.c
|
@ -769,11 +769,11 @@ static void rp_battles(struct stream *out, faction * f)
|
||||||
while (bm) {
|
while (bm) {
|
||||||
char buf[256];
|
char buf[256];
|
||||||
RENDER(f, buf, sizeof(buf), ("header_battle", "region", bm->r));
|
RENDER(f, buf, sizeof(buf), ("header_battle", "region", bm->r));
|
||||||
newline(out);
|
|
||||||
centre(out, buf, true);
|
centre(out, buf, true);
|
||||||
newline(out);
|
newline(out);
|
||||||
rp_messages(out, bm->msgs, f, 0, false);
|
rp_messages(out, bm->msgs, f, 0, false);
|
||||||
bm = bm->next;
|
bm = bm->next;
|
||||||
|
newline(out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1237,39 +1237,17 @@ void report_region(struct stream *out, const region * r, faction * f)
|
||||||
if (edges)
|
if (edges)
|
||||||
newline(out);
|
newline(out);
|
||||||
for (e = edges; e; e = e->next) {
|
for (e = edges; e; e = e->next) {
|
||||||
bool first = true;
|
|
||||||
message *msg;
|
message *msg;
|
||||||
|
|
||||||
bufp = buf;
|
|
||||||
size = sizeof(buf) - 1;
|
|
||||||
for (d = 0; d != MAXDIRECTIONS; ++d) {
|
for (d = 0; d != MAXDIRECTIONS; ++d) {
|
||||||
if (!e->exist[d])
|
if (e->exist[d]) {
|
||||||
continue;
|
msg = msg_message(e->transparent ? "nr_border_transparent" : "nr_border_opaque",
|
||||||
/* this localization might not work for every language but is fine for de and en */
|
"object dir", e->name, d);
|
||||||
if (first)
|
nr_render(msg, f->locale, buf, sizeof(buf), f);
|
||||||
bytes = (int)str_strlcpy(bufp, LOC(f->locale, "nr_borderlist_prefix"), size);
|
msg_release(msg);
|
||||||
else if (e->lastd == d)
|
paragraph(out, buf, 0, 0, 0);
|
||||||
bytes = (int)str_strlcpy(bufp, LOC(f->locale, "nr_borderlist_lastfix"), size);
|
}
|
||||||
else
|
|
||||||
bytes = (int)str_strlcpy(bufp, LOC(f->locale, "nr_borderlist_infix"), size);
|
|
||||||
if (wrptr(&bufp, &size, bytes) != 0)
|
|
||||||
WARN_STATIC_BUFFER();
|
|
||||||
bytes = (int)str_strlcpy(bufp, LOC(f->locale, directions[d]), size);
|
|
||||||
if (wrptr(&bufp, &size, bytes) != 0)
|
|
||||||
WARN_STATIC_BUFFER();
|
|
||||||
first = false;
|
|
||||||
}
|
}
|
||||||
/* TODO name is localized? Works for roads anyway... */
|
|
||||||
/* TODO: creating messages during reporting makes them not show up in CR? */
|
|
||||||
msg = msg_message("nr_borderlist_postfix", "transparent object",
|
|
||||||
e->transparent, e->name);
|
|
||||||
bytes = (int)nr_render(msg, f->locale, bufp, size, f);
|
|
||||||
msg_release(msg);
|
|
||||||
if (wrptr(&bufp, &size, bytes) != 0)
|
|
||||||
WARN_STATIC_BUFFER();
|
|
||||||
|
|
||||||
*bufp = 0;
|
|
||||||
paragraph(out, buf, 0, 0, 0);
|
|
||||||
}
|
}
|
||||||
if (edges) {
|
if (edges) {
|
||||||
while (edges) {
|
while (edges) {
|
||||||
|
@ -1299,7 +1277,6 @@ static void statistics(struct stream *out, const region * r, const faction * f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* print */
|
/* print */
|
||||||
newline(out);
|
|
||||||
m = msg_message("nr_stat_header", "region", r);
|
m = msg_message("nr_stat_header", "region", r);
|
||||||
nr_render(m, f->locale, buf, sizeof(buf), f);
|
nr_render(m, f->locale, buf, sizeof(buf), f);
|
||||||
msg_release(m);
|
msg_release(m);
|
||||||
|
@ -2305,8 +2282,10 @@ report_plaintext(const char *filename, report_context * ctx,
|
||||||
report_travelthru(out, r, f);
|
report_travelthru(out, r, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wants_stats && r->seen.mode >= seen_unit)
|
if (wants_stats && r->seen.mode >= seen_unit) {
|
||||||
statistics(out, r, f);
|
statistics(out, r, f);
|
||||||
|
newline(out);
|
||||||
|
}
|
||||||
|
|
||||||
/* Nachrichten an REGION in der Region */
|
/* Nachrichten an REGION in der Region */
|
||||||
if (r->seen.mode >= seen_travel) {
|
if (r->seen.mode >= seen_travel) {
|
||||||
|
|
|
@ -275,6 +275,11 @@ void sbs_strcpy(struct sbstring *sbs, const char *str)
|
||||||
sbs->end = sbs->begin + len;
|
sbs->end = sbs->begin + len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t sbs_length(const struct sbstring *sbs)
|
||||||
|
{
|
||||||
|
return sbs->end - sbs->begin;
|
||||||
|
}
|
||||||
|
|
||||||
char *str_unescape(char *str) {
|
char *str_unescape(char *str) {
|
||||||
char *read = str, *write = str;
|
char *read = str, *write = str;
|
||||||
while (*read) {
|
while (*read) {
|
||||||
|
|
|
@ -50,6 +50,7 @@ extern "C" {
|
||||||
void sbs_strcat(struct sbstring *sbs, const char *str);
|
void sbs_strcat(struct sbstring *sbs, const char *str);
|
||||||
void sbs_strncat(struct sbstring *sbs, const char *str, size_t size);
|
void sbs_strncat(struct sbstring *sbs, const char *str, size_t size);
|
||||||
void sbs_strcpy(struct sbstring *sbs, const char *str);
|
void sbs_strcpy(struct sbstring *sbs, const char *str);
|
||||||
|
size_t sbs_length(const struct sbstring *sbs);
|
||||||
|
|
||||||
/* benchmark for units:
|
/* benchmark for units:
|
||||||
* JENKINS_HASH: 5.25 misses/hit (with good cache behavior)
|
* JENKINS_HASH: 5.25 misses/hit (with good cache behavior)
|
||||||
|
|
Loading…
Reference in a new issue