#!/usr/libexec/platform-python

"""
    workspace++

    ws_send_ical

    python version of ws_send_ical command, no privileges necessary

    send a rminder as VCALENDAR entry for a named workspace to the 
    address. 

    Reads new YAML configuration files and new YAML workspace database.

    differences to old workspace version
       rewritten using python, 1st version bash-script

    Thomas Beisel Januar 2017, 2018, 2019, 2020

    based on (c) Holger Berger 2013, 2014, 2015, 2016, 2017 ws_list version


    workspace++ is based on workspace by Holger Berger, Thomas Beisel and Martin Hecht

    workspace++ is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    workspace++ is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with workspace++.  If not, see <http://www.gnu.org/licenses/>.

"""

from __future__ import print_function
#import yaml
import os, os.path, pwd, grp, sys
import glob, time
from optparse import OptionParser
from tempfile import mkstemp
### from datetime import datetime, date, time
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.utils import COMMASPACE, formatdate
from email import encoders
import datetime

CRLF = "\r\n"

# read a single line from ws.conf of the form: pythonpath: /path/to/python
def read_python_conf():
    for l in open("/etc/ws.conf","r"):
        if 'pythonpath' in l:
            key=l.split(":")[0].strip()
            value=l.split(":")[1].strip()
            if key == 'pythonpath':
                if os.path.isdir(value):
                    sys.path.append(value)
                    break
                else:
                    print("Warning: Invalid pythonpath in ws.conf", file=sys.stderr)

read_python_conf()

import yaml

class struct: pass
space2fs={}
spaces=[]


def send_ical(entry, smtphost, resource, attendee, mail_from, login, ws_name, create_time):
    entry_hash = hash( entry.name + str( entry.expiration) )
    
    exp_time = time.localtime( entry.expiration )   # time ws expires
    exp_time_str = time.strftime("%Y%m%dT%H%M00", exp_time ) # convert format
    ##  start time for calendar entry is 1 hour before expiration time of the ws
    start_time_str = time.strftime("%Y%m%dT%H%M00", time.localtime( entry.expiration -3600 ) )

    ### Im EVENT Bereich wollten wir das Senden einer Antwortmail unterdruecken, aber hier gibt
    ### es ein Problem mit dem bei einem Kunden eingestzten Note System. Dieses sendet immer eine 
    ### Antwort, die aber durch unsere Netzkonfig nicht ankommen kann...
    ## so nicht ## ical += "ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;RSVP=FALSE" + CRLF
    ## so nicht ## ical += "ATTENDEE;CUTYPE=RESOURCE;ROLE=NON-PARTICIPANT;RSVP=FALSE" + CRLF
    ## so nicht ## ical += "ATTENDEE;CUTYPE=RESOURCE;RSVP=FALSE" + CRLF
    ## ical += "ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=FALSE" + CRLF

    ##   Header _____________________________________
    ical  = "BEGIN:VCALENDAR" + CRLF
    ical += "PRODID:-//HLRS Cluster Team//Workspace V2.1//EN" + CRLF
    ical += "VERSION:2.0" + CRLF
    ical += "METHOD:REQUEST" + CRLF
    ical += "BEGIN:VTIMEZONE" + CRLF
    ical += "TZID:Europe/Berlin" + CRLF
    ical += "BEGIN:DAYLIGHT" + CRLF
    ical += "TZOFFSETFROM:+0100" + CRLF
    ical += "TZOFFSETTO:+0200" + CRLF
    ical += "TZNAME:CEST" + CRLF
    ical += "DTSTART:19700329T020000" + CRLF
    ical += "RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3" + CRLF
    ical += "END:DAYLIGHT" + CRLF
    ical += "BEGIN:STANDARD" + CRLF
    ical += "TZOFFSETFROM:+0200" + CRLF
    ical += "TZOFFSETTO:+0100" + CRLF
    ical += "TZNAME:CET" + CRLF
    ical += "DTSTART:19701025T030000" + CRLF
    ical += "RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10" + CRLF
    ical += "END:STANDARD" + CRLF
    ical += "END:VTIMEZONE" + CRLF
    ical += "X-MS-OLK-FORCEINSPECTOROPEN:TRUE" + CRLF

    ## Event _______________________________________

    ical += "BEGIN:VEVENT" + CRLF 
    ical += "CREATED:" + create_time + CRLF 
    ical += "DTSTAMP:" + create_time + CRLF 
    ical += "UID:587a1aa6-" + str( entry_hash ) + CRLF 
    ical += "DESCRIPTION:Workspace " + ws_name + " will be deleted on host " + resource + CRLF 
    ical += "LOCATION:" + resource + CRLF
    ical += "SUMMARY: Workspace " + ws_name + " expires" + CRLF 
    ical += "DTSTART;TZID=Europe/Berlin:" + start_time_str + CRLF 
    ical += "DTEND;TZID=Europe/Berlin:" + exp_time_str + CRLF 
    ical += "LAST-MODIFIED:" + create_time + CRLF
    ical += "CLASS:PRIVATE" + CRLF 
    ical += "X-MICROSOFT-CDO-BUSYSTATUS:BUSY" + CRLF
    ical += "X-MICROSOFT-DISALLOW-COUNTER:TRUE" + CRLF
    ical += "END:VEVENT" + CRLF 
    ical += "END:VCALENDAR" + CRLF 

    ##  ___ now compose the mail header and send ________________

    eml_body = "Workspace " + ws_name + " on host " + resource + " is going to expire "
    msg = MIMEMultipart('mixed')
    msg['Reply-To'] = mail_from
    msg['Date'] = formatdate(localtime=True)
    msg['Subject'] = "Workspace expire on " + exp_time_str
    msg['From'] = mail_from
    msg['To'] = attendees
    
    part_email = MIMEText(eml_body,"html")
    part_cal = MIMEText(ical,'calendar;method=REQUEST')   # FIXME method=REQUEST ay be wrong here, is not in calendar entry
    
    msgAlternative = MIMEMultipart('alternative')
    msg.attach(msgAlternative)
    
    msgAlternative.attach(part_email)
    msgAlternative.attach(part_cal)
     
    mailServer = smtplib.SMTP(smtphost)
    #mailServer.ehlo()
    #mailServer.starttls()
    #mailServer.ehlo()
    #mailServer.login(login, password)
    mailServer.sendmail(mail_from, attendees, msg.as_string())
    mailServer.close()

    print("Sent reminder for workspace " + ws_name + " to " + attendees + " please do not forget to accept invitation")


# we have to find out if the calling user is admin before we can process commandline,
# so we have to determine user and read config first, and parse commandline last

# who are we?
login = os.getlogin()
create_time = time.strftime("%Y%m%dT%H%M00", time.localtime(None))
uid = os.getuid()
gid = os.getgid()
user = pwd.getpwuid(uid)[0]
group = grp.getgrgid(gid)[0]
groups = [grp.getgrgid(gid_tmp)[0] for gid_tmp in os.getgroups()]

# load config file
config = yaml.load(open('/etc/ws.conf'))

smtphost  = config['smtphost']
resource = config['clustername']
try:
    mail_from = config['mail_from']
except KeyError:
    print("Warning: no mail_from in global config, please inform system administrator!", file=sys.stderr)
    mail_from = ""

# make root always admin, seeing admin options and admin output
# FIXME: eid or uid or both?
if os.geteuid()==0 or os.getuid()==0:
    admin = True
    print(' Error: this command is not for admins')
    sys.exit( 1 )


# option configuration
Usage  = "[-F filesystem | --filesystem filesystem] [-n|--workspace] workspacename [-m | --mail] mailadress\n"
Usage += "      this command is used to send a calendar invitation by Email to ensure users do not forget\n"
Usage += "      the expiration date of a workspace"
       
parser = OptionParser( Usage )
parser.add_option('-F', '--filesystem', dest='filesystem', help='filesystem where the workspaces is located on')
parser.add_option('-m', '--mail', dest='mailadress', help='your mail address to send to')
parser.add_option('-n', '--workspace', dest='workspace', help='name of selected workspaces')

# print sys.argv
(options, args) = parser.parse_args()
# print 'args ', args

opts_used = 0

if options.workspace == None:
    if len(args) == 0:
        print("Error: need to specify workspace name", file=sys.stderr)
        print("\nusage: " + Usage, file=sys.stderr)
        sys.exit(2)
    else:
        options.workspace = args[0]
        opts_used = 1

if options.mailadress == None:
    if len(args) - opts_used == 0:
        try:
            attendees = open(os.path.expanduser("~/.ws_user.conf")).readline()
        except:
            attendees = ""
        if ":" in attendees:
            try:
                user_config = yaml.load(open(os.path.expanduser("~/.ws_user.conf")))
                attendees = user_config["mail"]
            except:
                attendees = ""
        if attendees == "":
            print("Error: need to specify mailaddress", file=sys.stderr)
            print("\nusage: " + Usage, file=sys.stderr)
            sys.exit(2)   
        else:
            print("Info: took email address",attendees,"from ~/.ws_user.conf", file=sys.stderr)
    else:
        # arg_idx = len(args) - opts_used - 1
        attendees = args[opts_used]
else:
    attendees = options.mailadress

ws_name   = options.workspace

# all filesystems or a selected one?
if(options.filesystem): 
    if options.filesystem in config['workspaces']:
        filesystems = [options.filesystem]
    else:
        print("Error: no such filesystem.", file=sys.stderr)
        sys.exit(-1)
else:
    filesystems = list(config['workspaces'].keys())

# reduce list to allowed filesystems
legal=[]
for f in filesystems:
    userok=True
    if (('user_acl' in config['workspaces'][f] and len(config['workspaces'][f]['user_acl'])>0 ) or 
        ('group_acl' in config['workspaces'][f] and len(config['workspaces'][f]['group_acl'])>0)):
        userok=False
    if 'group_acl' in config['workspaces'][f]:
        for g in groups:
            if g in config['workspaces'][f]['group_acl']:
                userok=True
                break
        if group in config['workspaces'][f]['group_acl']:
            userok=True
    if 'user_acl' in config['workspaces'][f]:
        if user in config['workspaces'][f]['user_acl']:
            userok=True
    # 
    if userok:
        legal.append(f)


# create mapping from spaces to filesystems
for f in legal:
    for s in config['workspaces'][f]["spaces"]:
        space2fs[s]=f
        spaces.append(s)

found_a_ws = False

for fs in legal:
    pattern = os.path.join(config['workspaces'][fs]['database'],user+'-*')
    for    ws in glob.glob(pattern):
        entry = struct()
        entry.name = ws
        content = yaml.load(open(ws))
        entry.creation = os.path.getctime(ws)
        try:
            entry.expiration = int(content['expiration'])
            entry.extensions = content['extensions']
            entry.acctcode = content['acctcode']
            entry.workspace = content['workspace']
            entry.reminder = int(content['reminder'])
            entry.mailaddress = content['mailaddress']
        except TypeError:  
            # fallback to old file format
            f=open(ws)
            entry.expiration = int(f.readline())
            entry.workspace = f.readline()[:-1]
            entry.acctcode = f.readline()[:-1].split(":")[1]
            entry.extensions = int(f.readline()[:-1].split(":")[1])
            entry.reminder = 0
            entry.mailaddress = ""
            f.close()

        if os.path.basename(entry.name)[os.path.basename(entry.name).find('-')+1:]  == options.workspace:
            if time.time() > entry.expiration:
                print("Error: your workspace " + ws_name + " has been expired!!")
                print("use the ws_restore command to recover this workspace...")
                sys.exit( 3 )
            
            send_ical(entry, smtphost, resource, attendees, mail_from, login, ws_name, create_time)
            found_a_ws = True

if found_a_ws == False:
    print("Sorry, there is no workspace " + ws_name + " available on this system!!") 
    print("if you think there should be such a workspace, please check the output of")
    print("    ws_restore -l")
    print("for removed workspaces which are possible to recover")
    print("or run the ws_list and double check for a typo of the workspace name")
    sys.exit(2)
