/* ** $VER: CfgSortMail.thor 4.3 (24.2.99) ** by Eirik Nicolai Synnes ** ** See SortMail.guide for documentation ** */ options results options failat 31 /*signal on error*/ signal on syntax signal on break_c signal on halt parse arg arguments /* ** Initialize some variables */ globals = 'version mvhelp thorport thorpath cfgfile retfield2 data. head. text. textread globalcfg. trigger. temp. atemp. stemp. menulist. bbslist. conflist. bbsdata. cursys. progwin currentmsg. addrs. BBSREAD.LASTERROR THOR.LASTERROR globals' version = subword(sourceline(2), 4, 1) cfgfile = 'SortMail.cfg' /* ** Check for Thor's ARexx port */ thorport = address() if left(thorport, 5) ~= 'THOR.' then do say 'CfgSortMail.thor can only be run from within Thor.' signal cleanup end /* ** Find/open BBSREAD ARexx port */ if ~show('P', 'BBSREAD') then do address(command) 'Run >NIL: `GetEnv THOR/THORPath`bin/LoadBBSRead' 'WaitForPort BBSREAD' if rc ~= 0 then call displayerror(30, 'Couldn''t open BBSREAD''s ARexx port.') end /* ** See if another copy of CfgSortMail is already running */ address(thorport) if getclip('CSM_Active') ~= '' then if ~reqnotify('Another copy of CfgSortMail is probably running.\nDo you want to continue?') then exit(0) call setclip('CSM_Active', 'True') /* ** Get Thor's path */ call open(pn, 'ENV:Thor/THORPATH', 'R') thorpath = readln(pn) call close(pn) /* ** Find the configuration file */ address(thorport) 'CURRENTSYSTEM STEM 'cursys if (rc > 1) then call displayerror(30, 'CURRENTSYSTEM: 'THOR.LASTERROR) address(bbsread) 'GETBBSDATA "'cursys.BBSNAME'" 'bbsdata if (rc ~= 0) then call displayerror(rc, 'GETBBSDATA: 'BBSREAD.LASTERROR) if (right(bbsdata.BBSPATH, 1) ~= ':') & (right(bbsdata.BBSPATH, 1) ~= '/') then bbsdata.BBSPATH = bbsdata.BBSPATH || '/' /* ** Get list of all systems */ address(bbsread) 'GETBBSLIST 'bbslist if rc > 0 then call displayerror(rc, 'GETBBSLIST:\n'BBSREAD.LASTERROR) /* ** Check Workbench version */ address command 'Version >NIL: workbench.library 40' if (rc = 0) then mvhelp = 1; else mvhelp = 0 /* ** Read configuration */ call readcfg() /* ** Display main menu */ mainmenu: drop movetrig menulist.1 = 'Email conference: 'globalcfg.CONFERENCE menulist.2 = 'Sort outgoing messages: '; if (symbol('globalcfg.LOCALTO') = 'VAR') then menulist.2 = menulist.2 || 'YES, to "'globalcfg.LOCALTO'"'; else menulist.2 = menulist.2 || 'NO' menulist.3 = '' menulist.4 = 'Add new trigger' menulist.5 = 'Edit a trigger' menulist.6 = 'Rearrange triggers' menulist.7 = 'Delete a trigger' menulist.8 = '' menulist.9 = 'Auto configuration' menulist.10 = '' menulist.11 = 'Save and exit' menulist.count = 11 choice = showmenu('CfgSortMail 'version, 7) select when choice = 0 then if reqnotify('Are you sure you want\nto exit without saving?') then signal cleanup when choice = 1 then do address(bbsread) 'GETCONFLIST BBSNAME "'globalcfg.SYSTEM'" STEM 'conflist if (rc > 0) then call displayerror(rc, 'GETCONFLIST:\n'BBSREAD.LASTERROR) if (conflist.COUNT = 0) then do reqnotify('No conferences created on this\nsystem. Using EMail as default.', 'Ok') globalcfg.CONFERENCE = 'EMail' end else do address(thorport) 'REQUESTLIST INSTEM 'conflist' TITLE "Select conference" SIZEGADGET' if rc > 5 then call displayerror(rc, 'REQUESTLIST:\n'THOR.LASTERROR) else if rc ~= 5 then globalcfg.CONFERENCE = result end drop conflist. end when choice = 2 then do if reqnotify('Sort outgoing messages?\n\nWhen you send messages, a copy will by default be added\nto the database. If this option is active then messages\nthat do not match any triggers will be written to a\nconfigurable conference.') then do address(bbsread) 'GETCONFLIST BBSNAME "'globalcfg.SYSTEM'" STEM 'conflist if rc > 0 then call displayerror(rc, 'GETCONFLIST:\n'BBSREAD.LASTERROR) address(thorport) menulist.1 = 'Create new conference ' menulist.2 = '' do i = 1 to conflist.count mcnt = i + 2; menulist.mcnt = center(i'.', 4) || conflist.i end menulist.count = i + 1 confchoice = showmenu('Select destination conference') if confchoice = 1 then do call reqstring('Destination conference', 'Enter new conference name.\n\nThe new conference will be created\nwhen the configuration is saved.', 'Sent mail', 64, 1) if result ~= '' then globalcfg.LOCALTO = result end else if confchoice > 2 then do cno = confchoice - 2; globalcfg.LOCALTO = conflist.cno end drop conflist. end else drop globalcfg.LOCALTO end when choice = 4 then call cfgtrigger(1) when choice = 5 then call cfgtrigger(2) when choice = 6 then do while movetrig ~= 0 do i = 1 to trigger.count; menulist.i = center(i'.', 4) || trigger.i.name; end menulist.count = trigger.count if trigger.count = 0 then do movetrig = 0; call reqnotify('There are no triggers to rearrange.', 'Bummer') end else do movetrig = showmenu('Select trigger to move, Cancel when done') if movetrig = 0 then break temptrig = trigger.count + 1 if reqnotify('Move trigger up or down?', '_Up|_Down') then desttrig = movetrig - 1; else desttrig = movetrig + 1 if (desttrig ~= 0) & (desttrig <= trigger.count) then do call copystem(20, movetrig, temptrig) call copystem(20, desttrig, movetrig) call copystem(20, temptrig, desttrig) end else call reqnotify('Can''t move trigger there.', 'Ops') end drop trigger.temptrig. temptrig desttrig mcnt end when choice = 7 then do do i = 1 to trigger.count; menulist.i = center(i'.', 4) || trigger.i.name; end menulist.count = trigger.count if trigger.count = 0 then call reqnotify('There are no triggers to delete.', 'Bummer') else do deltrig = showmenu('Select trigger to delete') if deltrig = 0 then break if ~reqnotify('Are you sure you want to\nwant to delete this trigger:\n'trigger.deltrig.name'.') then break tcnt = 0 do i = 1 to trigger.count if i ~= deltrig then do; tcnt = tcnt + 1; if tcnt ~= i then call copystem(20, i, tcnt); end end trigger.count = trigger.count - 1; i = i - 1 end drop trigger.i. deltrig end when choice = 9 then call autoconfig() when choice = 11 then do call writecfg() signal cleanup end otherwise nop end signal mainmenu error: syntax: select when symbol('BBSREAD.LASTERROR') = 'VAR' then call displayerror(rc, 'Line 'sigl': 'BBSREAD.LASTERROR) when symbol('THOR.LASTERROR') = 'VAR' then call displayerror(rc, 'Line 'sigl': 'THOR.LASTERROR) otherwise call displayerror(30, 'Error 'rc' in line 'sigl': 'errortext(rc)) end break_c: halt: cleanup: /* ** Close progress window if open */ if symbol('progwin') = 'VAR' then do address(thorport) 'CLOSEPROGRESS REQ 'progwin drop progwin end /* ** Have a nice day */ call setclip('CSM_Active') exit(0) /**************************************************************************** ********************************* Auto Configure ****************************** ****************************************************************************/ autoconfig: interpret 'procedure expose 'globals parse arg cfgtype found = 0; foundsrch = 0; isdigest = 0 newtrig = trigger.count + 1 address(thorport) if ~(reqnotify('Auto configure.\n\nThis option will examine the message currently\ndisplayed in the Thor main window and try to\ncreate a new trigger based on the contents of\nthe message.\n\nCurrently it recognizes Aminet updates and\nsingle and digest messages from mailing lists.', 'Continue|Abort')) then return(0) if (cursys.CONFNAME = '') then do call reqnotify('You must enter a system and make sure a\nmessage is displayed in Thor''s main\nwindow before using auto configure.', 'Abort') return(0) end 'CURRENTMSG STEM 'curmsg if (rc = 30) & (THOR.LASTERROR = 'No current message.') then do call reqnotify('You must enter a system and make sure a\nmessage is displayed in Thor''s main\nwindow before using auto configure.', 'Abort') drop THOR.LASTERROR return(0) end if (rc ~= 0) then call displayerror(30, 'CURRENTSYSTEM: 'THOR.LASTERROR) /* if (curmsg.CONFNAME ~= 'EMail') then do call reqnotify('Auto config requires that the message you\nwant to examine is in the EMail conference.', 'Abort') return(0) end */ address(bbsread) 'READBRMESSAGE "'curmsg.BBSNAME'" "'curmsg.CONFNAME'" 'curmsg.MSGNR' HEADSTEM 'head' TEXTSTEM 'text if (rc ~= 0) then call displayerror(rc, 'READBRMESSAGE:\n'BBSREAD.LASTERROR) address(thorport) /* ** Aminet daily and weekly mailings */ if (head.TOADDR = 'aminet-daily@wugate.wustl.edu') | (head.TOADDR = 'aminet-weekly@wugate.wustl.edu') then do found = 1 if ~(reqnotify('Recognized message as an Aminet file index update.\n\nDo you want to add a trigger for adding the list\nof files in these messages to the file database?')) then return(0) trigger.newtrig.name = 'Aminet update' trigger.newtrig.groups = 'EMail' trigger.newtrig.deluser = 1 trigger.newtrig.matchall = 1 trigger.newtrig.nolocal = 0 trigger.newtrig.search.count = 1 trigger.newtrig.search.1.type = 'TO' trigger.newtrig.search.1.criteria = head.TOADDR trigger.newtrig.search.1.not = 0 trigger.newtrig.action.1.type = 'RECENT' trigger.newtrig.action.1.dontadd = 0 trigger.newtrig.action.1.checkdupes = 0 trigger.newtrig.action.1.nostats = 0 trigger.newtrig.action.2.type = 'KILL' trigger.newtrig.action.count = 2 trigger.count = trigger.count + 1 signal acfini end /* ** Mailing lists */ if ~(found) then do if (symbol('text.COMMENT.COUNT') = 'VAR') then if (text.COMMENT.COUNT > 0) then do i = 1 to text.COMMENT.COUNT while foundsrch = 0 header = '' if (upper(subword(text.COMMENT.i, 1, 1)) = 'SENDER:') then header = subword(text.COMMENT.i, 1, 1) else if (upper(subword(text.COMMENT.i, 1, 1)) = 'RESENT-SENDER:') then header = subword(text.COMMENT.i, 1, 1) else if (upper(subword(text.COMMENT.i, 1, 1)) = 'RETURN-PATH:') then header = subword(text.COMMENT.i, 1, 1) if (header ~= '') then do /* This stuff should work for listserv, qmail, majordomo and petidomo lists */ added = '' if (index(upper(subword(text.COMMENT.i, words(text.COMMENT.i), 1)), 'OWNER') > 0) then added = 'OWNER' else if (index(upper(subword(text.COMMENT.i, words(text.COMMENT.i), 1)), 'REQUEST') > 0) then added = 'REQUEST' else if (index(upper(subword(text.COMMENT.i, words(text.COMMENT.i), 1)), 'ERRORS') > 0) then added = 'ERRORS' else if (index(upper(subword(text.COMMENT.i, words(text.COMMENT.i), 1)), 'BOUNCE') > 0) then added = 'BOUNCE' if (added ~= '') then do /* fulladdr is the full search criteria */ fulladdr = compress(subword(text.COMMENT.i, words(text.COMMENT.i), 1), '<>()') /* stripaddr is the reply address to be used */ stripaddr = '' if (left(upper(fulladdr), 6) = added || '-') then stripaddr = substr(fulladdr, length(added) + 2) else do cutpos = index(upper(fulladdr), '-' || added) if (cutpos > 0) then stripaddr = substr(fulladdr, 1, cutpos - 1) ||, substr(fulladdr, index(fulladdr, '@')) end if (stripaddr ~= '') then do /* make a trigger */ trigger.newtrig.search.count = 2 trigger.newtrig.search.1.type = 'HEADER' trigger.newtrig.search.1.keyword = header trigger.newtrig.search.1.criteria = fulladdr trigger.newtrig.search.1.not = 0 trigger.newtrig.search.2.type = 'TO' trigger.newtrig.search.2.pattern = stripaddr trigger.newtrig.search.2.not = 0 trigger.newtrig.action.1.replyaddr = stripaddr foundsrch = 1 end end end else if ((foundsrch = 0) &, (upper(subword(text.COMMENT.i, 1, 3)) = 'DELIVERED-TO: MAILING LIST') &, (words(text.COMMENT.i) > 3)) then do /* This stuff should work for ezml lists */ header = subword(text.COMMENT.i, 1, 1) address = subword(text.COMMENT.i, 4, 1) /* To address was similar to return path address */ trigger.newtrig.search.count = 2 trigger.newtrig.search.1.type = 'HEADER' trigger.newtrig.search.1.keyword = header trigger.newtrig.search.1.criteria = address trigger.newtrig.search.1.not = 0 trigger.newtrig.search.2.type = 'TO' trigger.newtrig.search.2.pattern = stripaddr trigger.newtrig.search.2.not = 0 trigger.newtrig.action.1.replyaddr = address foundsrch = 1 end end if (foundsrch) then do /* See if this may be digest message of some kind */ if ((symbol('text.PART.COUNT') = 'VAR') & (text.PART.COUNT > 0)) |, (text.TEXT.COUNT > 200) |, (index(trigger.newtrig.search.1.criteria, 'digest') > 0) then do call reqnotify('This message looks like it could be a digest message.\n\nIn order to set up a correct trigger I will have\nto know if this message is a digest message or a\nsingle, ordinary mailing list message.\n\nDigest messages are collections of messages posted\nto mailing list and are usually sent out once a\nday. SortMail can split digests into seperate\nmessages for easier reading.', 'Digest|_Single|Abort') if (result = 0) then return(0) if (result = 1) then isdigest = 1 end end if (foundsrch) & (symbol('trigger.newtrig.action.1.replyaddr') ~= 'VAR') then do call parseaddr(1) mcnt = 0 do i = 1 to addrs.count if (addrs.i.addr = '') then iterate i mcnt = mcnt + 1 menulist.mcnt = addrs.i.name || ' <' || addrs.i.addr || '>' end menulist.count = mcnt if (menulist.count = 0) then do call reqnotify('I could not determine the address used for\nposting messages on the mailing list, nor\ncould I find any addresses used in this\nmessage. You will have to configure a\ntrigger for this mailing list yourself.', 'Ok') return(0) end call reqnotify('I could not determine the address used for\nposting messages on the mailing list.\n\nNext a list of addresses given in this\nmessage will appear. Select the correct\none or, if you do not find it, cancel the\nrequester.', 'Ok') choice = showmenu('Select list address') if (choice = 0) then return(0) trigger.newtrig.action.1.replyaddr = addrs.choice.addr; foundsrch = 1 atpos = index(trigger.newtrig.action.1.replyaddr, '@') if (atpos > 0) then tname = substr(trigger.newtrig.action.1.replyaddr, 1, atpos - 1) else tname = trigger.newtrig.action.1.replyaddr end else do atpos = index(trigger.newtrig.action.1.replyaddr, '@') if (atpos > 0) then tname = substr(trigger.newtrig.action.1.replyaddr, 1, atpos - 1) else tname = trigger.newtrig.action.1.replyaddr call reqstring('Reply address','The address below looks like it could be the\naddress used for sending messages to the\nmailinglist. It can be used to direct\nany replies you make to the mailing list,\notherwise you will sometimes have to edit\nthe address when replying to messages from\nthe list.\n\nEdit it if necessary, or hit cancel if you\ndo not want this address to be used as the\ndefault reply address.', trigger.newtrig.action.1.replyaddr, 64, 1,, 0) if (result = '') then drop trigger.newtrig.action.1.replyaddr else trigger.newtrig.action.1.replyaddr = result end if (foundsrch) then do trigger.newtrig.groups = 'EMail' trigger.newtrig.deluser = 1 trigger.newtrig.matchall = 0 trigger.newtrig.nolocal = 1 if (isdigest) then do trigger.newtrig.action.1.type = 'SPLITDIGEST' trigger.newtrig.action.2.type = 'KILL' trigger.newtrig.action.count = 2 end else do trigger.newtrig.action.1.type = 'MOVE' trigger.newtrig.action.count = 1 end call reqstring('Auto config', 'You can now create a trigger for moving messages\nposted to this mailing list to another conference.\n\nThe trigger name could for example be the address\nof the mailing list, which is what is suggested\nbelow. The name for the trigger will only be\nused to identify this particular trigger in\nCfgSortMail and the log.\n\nHit cancel if you do not want to create a new\ntrigger.', tname, 64, 1) if (result = '') then do; found = 0; foundsrch = 0; drop trigger.newtrig.; signal mlfini; end else trigger.newtrig.name = result call reqstring('Auto config','Enter a name for the conference messages\nbelonging to this mailing list should be\ncopied to.', '(ML) ', 64, 1) if (result = '') then do; found = 0; foundsrch = 0; drop trigger.newtrig.; signal mlfini; end else trigger.newtrig.action.1.destconf = result found = 1; trigger.count = newtrig end end mlfini: acfini: if ~(found) then call reqnotify('This message did not contain any elements\nCfgSortMail recognized. A trigger will\nhave to be configured manually.', 'Ok') else call reqnotify('The new trigger has been created. Please verify\nthat the information is correct by selecting\n"Edit trigger" in the main menu and then\n' || newtrig || '. ' || trigger.newtrig.name, 'Ok') drop found return(0) /**************************************************************************** *************** Put addresses and names in a string into a stem *************** ****************************************************************************/ parseaddr: interpret 'procedure expose 'globals parse arg checkcc i = 1; acnt = 0; usedhead = 0; drop addrs. if (symbol('head.TOADDR') = 'VAR') & ~(index(head.TOADDR, ',') > 0) then do acnt = acnt + 1; addrs.acnt.name = ''; addrs.acnt.cc = 0; usedhead = 1 addrs.acnt.addr = head.TOADDR if (symbol('head.TONAME') = 'VAR') then addrs.acnt.name = head.TONAME end if (symbol('text.COMMENT.COUNT') = 'VAR') then if (text.COMMENT.COUNT > 0) then do while i <= text.COMMENT.COUNT thiscc = 0 if (checkcc = 1) & (upper(subword(text.COMMENT.i, 1, 1)) = 'CC:') then thiscc = 1 if (thiscc) | (upper(subword(text.COMMENT.i, 1, 1)) = 'TO:') then do addrs = subword(text.COMMENT.i, 2) do forever addrs = strip(addrs, 'B', ' ' || '09'x) offset = 1 do forever length = index(substr(addrs, offset), ','); if (length = 0) then length = length(addrs) - offset + 1 thisaddr = strip(substr(addrs, offset, length), 'B', ', '); acnt = acnt + 1; addrs.acnt.addr = ''; addrs.acnt.name = '' if (thiscc) then addrs.acnt.cc = 1; else addrs.acnt.cc = 0 if (words(thisaddr) = 1) then addrs.acnt.addr = strip(thisaddr, 'B', '<>()') else if (index(thisaddr, '<') > 0) then do addrstart = index(thisaddr, '<') addrlength = index(substr(thisaddr, addrstart), '>') addrs.acnt.addr = strip(substr(thisaddr, addrstart + 1, addrlength), 'B', '> ') addrs.acnt.name = strip(delstr(thisaddr, addrstart, addrlength), 'B', ' "' || '27'x) end else do j = 1 to words(thisaddr) thispart = strip(subword(thisaddr, j, 1), 'B', '<>" ' || '27'x) if (index(thispart, '@') > 0) then addrs.acnt.addr = thispart else addrs.acnt.name = addrs.acnt.name || thispart || ' ' end if ~(thiscc) & (usedhead) & (addrs.acnt.addr = addrs.1.addr) & (addrs.acnt.name = addrs.1.name) then do drop addrs.acnt.; acnt = acnt - 1 end if (offset + length >= length(addrs)) then break offset = offset + length end j = i + 1; if ~((c2d(left(text.COMMENT.j, 1)) = 9) | (c2d(left(text.COMMENT.j, 1)) = 32)) then break i = i + 1; addrs = text.COMMENT.i end end i = i + 1 end addrs.COUNT = acnt return(0) /**************************************************************************** ****************************** Configure trigger ****************************** ****************************************************************************/ cfgtrigger: interpret 'procedure expose 'globals parse arg cfgtype if cfgtype = 1 then do /* Add trigger */ temp.name = 'Change me' temp.groups = 'EMail' temp.deluser = 1 temp.matchall = 1 temp.nolocal = 0 temp.action.count = 0 temp.search.count = 0 org = trigger.count + 1 end if cfgtype = 2 then do /* Edit trigger */ if trigger.count = 0 then do call reqnotify('There are no triggers to configure.', 'Bummer') return(0) end do i = 1 to trigger.count; menulist.i = center(i'.', 4) || trigger.i.name; end menulist.count = trigger.count org = showmenu('Select trigger to edit') if org = 0 then return(0) call copystem(1, org) end triggermenu: menulist.1 = 'Name: 'temp.name menulist.2 = 'Search in: 'temp.groups menulist.3 = '' menulist.4 = 'Add outgoing messages: '; if temp.NOLOCAL then menulist.4 = menulist.4 || 'NO'; else menulist.4 = menulist.4 || 'YES' menulist.5 = 'Add user: '; if ~temp.deluser then menulist.5 = menulist.5 || 'YES'; else menulist.5 = menulist.5 || 'NO' menulist.6 = 'Match all criterias: '; if temp.matchall then menulist.6 = menulist.6 || 'YES'; else menulist.6 = menulist.6 || 'NO' menulist.7 = '' menulist.8 = 'Add search entry' menulist.9 = 'Add action entry' menulist.10 = '' menulist.11 = 'Search entries:'; sline = 11 if temp.search.count > 0 then do i = 1 to temp.search.count lno = sline + i if (symbol('temp.search.i.pattern') = 'VAR') then do menulist.lno = 'Pattern'; crit = temp.search.i.pattern end else do menulist.lno = 'String '; crit = temp.search.i.criteria end if (temp.search.i.not) then menulist.lno = menulist.lno || ' NOT'; else menulist.lno = menulist.lno || ' ' select when temp.search.i.type = 'TO' then menulist.lno = menulist.lno || ' in to: 'crit when temp.search.i.type = 'FROM' then menulist.lno = menulist.lno || ' in from: 'crit when temp.search.i.type = 'SUBJECT' then menulist.lno = menulist.lno || ' in subject: 'crit when temp.search.i.type = 'REPLYTO' then menulist.lno = menulist.lno || ' in Reply-To: 'crit when temp.search.i.type = 'BODY' then menulist.lno = menulist.lno || ' in message body: 'crit when temp.search.i.type = 'HEADER' then menulist.lno = menulist.lno || ' in header field: "'temp.search.i.keyword'"-line: 'crit otherwise menulist.lno = menulist.lno || ' in unknown: 'crit end drop crit end else do; lno = sline + 1; menulist.lno = ' None'; end lno = lno + 1; menulist.lno = '' lno = lno + 1; menulist.lno = 'Action entries:'; aline = lno if temp.action.count > 0 then do i = 1 to temp.action.count lno = aline + i select when temp.action.i.type = 'COPY' then do; menulist.lno = ' Copy message: To "'; if symbol('temp.action.i.destsys') = 'VAR' then menulist.lno = menulist.lno||temp.action.i.destsys': '; menulist.lno = menulist.lno||temp.action.i.destconf'"'; if symbol('temp.action.i.replyaddr') = 'VAR' then menulist.lno = menulist.lno', ReplyAddr "'temp.action.i.replyaddr'"'; end when temp.action.i.type = 'MOVE' then do; menulist.lno = ' Move message: To "'; if symbol('temp.action.i.destsys') = 'VAR' then menulist.lno = menulist.lno||temp.action.i.destsys': '; menulist.lno = menulist.lno||temp.action.i.destconf'"'; if symbol('temp.action.i.replyaddr') = 'VAR' then menulist.lno = menulist.lno', ReplyAddr "'temp.action.i.replyaddr'"'; end when temp.action.i.type = 'RECENT' then do; menulist.lno = ' AmiNet RECENT: '; if temp.action.i.dontadd = 1 then menulist.lno = menulist.lno'Don''t add to database'; else do; menulist.lno = menulist.lno'Add to database, '; if temp.action.i.checkdupes = 1 then menulist.lno = menulist.lno'check for dupes. '; else menulist.lno = menulist.lno'don''t check for dupes. '; end; if temp.action.i.nostats = 1 then menulist.lno = menulist.lno'No MOTD & statistics'; else menulist.lno = menulist.lno'Show MOTD & statistics'; end when temp.action.i.type = 'SAVEMESSAGE' then do; menulist.lno = ' Save message: '; if symbol('temp.action.i.filename') = 'VAR' then menulist.lno = menulist.lno'Filename "'temp.action.i.filename'"'; else menulist.lno = menulist.lno'Directory "'temp.action.i.directory'"'; end when temp.action.i.type = 'SPLITDIGEST' then do; menulist.lno = ' Split digest: To "'temp.action.i.destconf'"'; if symbol('temp.action.i.replyaddr') = 'VAR' then menulist.lno = menulist.lno', ReplyAddr "'temp.action.i.replyaddr'"'; end when temp.action.i.type = 'FORWARD' then do; menulist.lno = ' Forward message: To "'temp.action.i.sendto'" in "'temp.action.i.destconf'"'; if (symbol('temp.action.i.textfile') = 'VAR') then menulist.lno = menulist.lno || ', File "'temp.action.i.textfile'"'; end when temp.action.i.type = 'MAIL' then do; menulist.lno = ' Mail text file: '; if (symbol('temp.action.i.sendto') = 'VAR') then menuist.lno = menulist.lno || 'To "'temp.action.i.sendto'" in '; else menulist.lno = menulist.lno || 'In '; menulist.lno = menulist.lno || '"'temp.action.i.destconf'", File "'temp.action.i.textfile'"'; end when temp.action.i.type = 'EXTERNAL' then do; menulist.lno = ' Ext. script: Script name "'temp.action.i.scriptname'"'; if symbol('temp.action.i.scriptopts') = 'VAR' then menulist.lno = menulist.lno', Options "'temp.action.i.scriptopts'"'; end when temp.action.i.type = 'KILL' then menulist.lno = ' Kill message' when temp.action.i.type = 'EMPHASIZE' then menulist.lno = ' Emphasize message' when temp.action.i.type = 'MARKREAD' then menulist.lno = ' Mark message as read' otherwise menulist.lno = ' Unknown: 'temp.action.i.type end menulist.lno = menulist.lno end else do; lno = aline + 1; menulist.lno = ' None'; end lno = lno + 1; menulist.lno = '' lno = lno + 1; menulist.lno = 'Accept and return' menulist.count = lno choice = showmenu('Trigger configuration', 8) select when choice = 0 then do if reqnotify('Are you sure you want to abort\nconfiguration of this trigger?') then do; drop temp.; return(0); end end when choice = 1 then temp.name = reqstring('Trigger name', 'Enter a name for the trigger.', addasterix(temp.name), 64) when choice = 2 then temp.groups = reqstring('Search in', 'Enter the conference names to search\nin. If you want to search more than\none group, use an AmigaDOS pattern\nto limit your search.', addasterix(temp.groups), 256) when choice = 4 then do answer = reqnotify('Add outgoing messages?\n\nBy default a local copy will be added to the\ndatabase when a message is sent. By selecting\n"No" local copies that match this trigger will\nnot be added to the database, nor will any of\nthe actions specified in this trigger be\nperformed on them.') if answer = 0 then temp.NOLOCAL = 1; else temp.NOLOCAL = 0 end when choice = 5 then do answer = reqnotify('Add user?\n\nDo you want users sending mail processed by this\ntrigger to be added to the user database?') if answer = 0 then temp.DELUSER = 1; else temp.DELUSER = 0 end when choice = 6 then temp.MATCHALL = reqnotify('Match all criterias?\n\nIf you select yes, then all the configured search\ncriterias must be fulfilled for a message to be\nprocessed. Otherwise only one criteria will do.') when choice = 8 then call cfgsearch(1) when choice = 9 then call cfgaction(1) when (choice = sline) | (choice = aline) then call reqnotify('Select an entry to\nedit or delete it.', 'I''ll do that') when (choice > sline) & (choice < (aline - 1)) & (temp.search.count > 0) then do call reqnotify('Do you want to edit or\ndelete this search entry?', 'Edit|_Delete|_Abort') if result = 1 then call cfgsearch(2, (choice - sline)) else if result = 2 then call delsearch(choice - sline) end when (choice > aline) & (choice < (lno - 1)) & (temp.action.count > 0) then do actionno = choice - aline if (temp.action.actionno.type = 'KILL') | (temp.action.actionno.type = 'MARKREAD') | (temp.action.actionno.type = 'EMPHASIZE') then do call reqnotify('There are no options to\nedit for this action. Do\nyou want to delete it?', 'Delete|Abort') if result = 1 then call delaction(actionno) end else do call reqnotify('Do you want to edit or\ndelete this action entry?', 'Edit|_Delete|_Abort') if result = 1 then call cfgaction(2, actionno) else if result = 2 then call delaction(actionno) end drop actionno end when choice = lno then do if temp.name = 'Change me' then do call reqstring('Trigger name', 'Trigger name has not been changed.\nPlease enter an apropriate name.', addasterix(temp.name), 64) end if cfgtype = 1 then do; trigger.count = trigger.count + 1; org = trigger.count; end call copystem(10, , org); drop temp. return(0) end otherwise nop end signal triggermenu return(0) /**************************************************************************** **************************** Configure action entry *************************** ****************************************************************************/ cfgaction: interpret 'procedure expose 'globals parse arg atype, ano aborted = 0 if atype = 1 then do menulist.1 = 'Copy message to another conference' menulist.2 = 'Move message to another conference' menulist.3 = 'Emphasize message' menulist.4 = 'Mark message as read' menulist.5 = 'Kill message' menulist.6 = 'Parse AmiNet RECENT message' menulist.7 = 'Save message to disk' menulist.8 = 'Split digest' menulist.9 = 'Forward message' menulist.10 = 'Mail text file' menulist.11 = 'Run an external ARexx script' menulist.count = 11 act = showmenu('Select action type', 13) select when act = 0 then return(0) when act = 1 then do; atemp.type = 'COPY'; atemp.destconf = ''; end when act = 2 then do; atemp.type = 'MOVE'; atemp.destconf = ''; end when act = 3 then atemp.type = 'EMPHASIZE' when act = 4 then atemp.type = 'MARKREAD' when act = 5 then atemp.type = 'KILL' when act = 6 then do; atemp.type = 'RECENT'; atemp.checkdupes = 0; atemp.dontadd = 0; atemp.nostats = 0; end when act = 7 then do; atemp.type = 'SAVEMESSAGE'; atemp.header = 0; atemp.append = 0; atemp.nobin = 0; end when act = 8 then do; atemp.type = 'SPLITDIGEST'; atemp.destconf = ''; end when act = 9 then do; atemp.type = 'FORWARD'; atemp.destconf = ''; atemp.sendto = ''; end when act = 10 then do; atemp.type = 'MAIL'; atemp.destconf = ''; atemp.sendto = ''; atemp.textfie = ''; end when act = 11 then do; atemp.type = 'EXTERNAL'; atemp.scriptname = ''; end otherwise nop end drop act end if atype = 2 then call copystem(2, ano) select when atemp.type = 'COPY' then do syschoice = getsystem() if (syschoice ~= 0) then do if (bbslist.syschoice ~= globalcfg.SYSTEM) then atemp.destsys = bbslist.syschoice else atemp.destsys = globalcfg.SYSTEM call getconference(atemp.destsys, 1) if (result ~= 0) then do if symbol('atemp.replyaddr') ~= 'VAR' then atemp.replyaddr = '' atemp.replyaddr = reqstring('Reply address', 'Enter an address replies should be addressed to.\nA reply address is required if you use Local2Email.\nIf no reply address is given then replies will\nbe directed to the author or the address given\nin the Reply-To: header field. To do so, cancel\nthis requester.\n', addasterix(atemp.replyaddr), 64, 1, 'replyaddr') if atemp.replyaddr = '' then drop atemp.replyaddr end else aborted = 1 end else aborted = 1 end when atemp.type = 'MOVE' then do syschoice = getsystem() if (syschoice ~= 0) then do if (bbslist.syschoice ~= globalcfg.SYSTEM) then atemp.destsys = bbslist.syschoice else atemp.destsys = globalcfg.SYSTEM call getconference(atemp.destsys, 1) if (result ~= 0) then do if symbol('atemp.replyaddr') ~= 'VAR' then atemp.replyaddr = '' atemp.replyaddr = reqstring('Reply address', 'Enter an address replies should be addressed to.\nA reply address is required if you use Local2Email.\nIf no reply address is given then replies will\nbe directed to the author or the address given\nin the Reply-To: header field. To do so, cancel\nthis requester.\n', addasterix(atemp.replyaddr), 64, 1, 'replyaddr') if atemp.replyaddr = '' then drop atemp.replyaddr end else aborted = 1 end else aborted = 1 end when atemp.type = 'EMPHASIZE' then do if ~(reqnotify('Emphasizing will cause messages to appear as\nhighlighted in Thor.\n\nNote that it will always work on the original\nmessage (which may have been moved) and any\ncopies made after this action.', 'Add emphasize|Cancel')) then aborted = 1 end when atemp.type = 'KILL' then do if ~(reqnotify('Kill will cause the original message (which\nmay have been moved) to be deleted after all\nthe actions configured for this trigger has\nbeen performed.', 'Add kill|Cancel')) then aborted = 1 end when atemp.type = 'MARKREAD' then do if ~(reqnotify('Mark as read will cause messages to be marked\nas read when written to the database.\n\nNote that it will always work on the original\nmessage (which may have been moved) and any\ncopies made after this action.', 'Add mark as read|Cancel')) then aborted = 1 end when atemp.type = 'RECENT' then do call reqnotify('Do you want to add new files to the file database?\nIf you select no, the new files will only be shown\nin the New Files window in Thor.') if result = 1 then atemp.DONTADD = 0; else atemp.DONTADD = 1 if ~atemp.DONTADD then atemp.CHECKDUPES = reqnotify('Do you want to check for duplicates\nwhen adding files to the file database?\nThis will slow down operation.') else drop atemp.CHECKDUPES call reqnotify('Sometimes a message of the day and statistics\nare added to the listings. Do you want to display\nthese? Some of them may contain valuable info.') if result = 1 then atemp.NOSTATS = 0; else atemp.NOSTATS = 1 end when atemp.type = 'SAVEMESSAGE' then do if reqnotify('Do you want to save to a specific\nfile or to a directory using the\nsubject as the file name?', 'File|_Directory') then do; drop atemp.directory; if symbol('atemp.filename') ~= 'VAR' then atemp.filename = ''; end else do; drop atemp.filename; if symbol('atemp.directory') ~= 'VAR' then atemp.directory = ''; end if symbol('atemp.filename') = 'VAR' then do pathpart = getpathpart(atemp.filename) filepart = getfilepart(atemp.filename) pattern = '' end else do pathpart = getpathpart(atemp.directory) filepart = '' pattern = '~(#?)' end call reqfile('Select path', pathpart, filepart, pattern) if (result ~= '') then do if symbol('atemp.filename') = 'VAR' then atemp.filename = result else do atemp.directory = result if right(atemp.directory, 1) ~= '/' & right(atemp.directory, 1) ~= ':' then atemp.directory = atemp.directory || '/' end if symbol('atemp.directory') = 'VAR' then do if reqnotify('Do you want to substitute part of the\nsubject with another string? This can\nbe done to shorten the filename of\nsaved messages in order to fit into\nAmigaDOS''s 35 character filename limit.') then do if symbol('atemp.substitute') ~= 'VAR' then atemp.substitute = '' atemp.substitute = reqstring('Substitute string', 'Enter the string you want to substitute.', addasterix(atemp.substitute), 256, 1) if atemp.substitute = '' then drop atemp.substitute else do if symbol('atemp.with') ~= 'VAR' then atemp.with = '' atemp.with = reqstring('Substitute with', 'Enter the string you want to substitute with.', addasterix(atemp.with), 256, 1) if atemp.with = '' then drop atemp.substitute atemp.with end end else drop atemp.substitute atemp.with end else drop atemp.substitute atemp.with atemp.HEADER = reqnotify('Include header?\n\nDo you want to include\nheaders in the saved messages?') atemp.APPEND = reqnotify('Append to file?\n\nDo you want to append messages if the\ndestination file already exists, then\nselect "YES". Otherwise the file\nwill be overwritten.') if reqnotify('Save binaries?\n\nDo you want binary file parts to be copied\nto the download directory, then select "YES".\nOtherwise no action will be taken when a file\npart is found.') then atemp.nobin = 0; else atemp.nobin = 1 end else aborted = 1 end when atemp.type = 'SPLITDIGEST' then do syschoice = getsystem() if (syschoice ~= 0) then do if bbslist.syschoice ~= globalcfg.SYSTEM then atemp.destsys = bbslist.syschoice else atemp.destsys = globalcfg.SYSTEM call getconference(atemp.destsys, 1) if (result ~= 0) then do if symbol('atemp.replyaddr') ~= 'VAR' then atemp.replyaddr = '' atemp.replyaddr = reqstring('Reply address', 'Enter an address replies should be addressed to.\n\nIf no reply address is given then replies will\nbe directed to the author or the address given\nin the Reply-To: header field. To do so, cancel\nthis requester.', addasterix(atemp.replyaddr), 64, 1, 'replyaddr') if atemp.replyaddr = '' then drop atemp.replyaddr end else aborted = 1 end else aborted = 1 end when atemp.type = 'FORWARD' then do call getconference(globalcfg.SYSTEM, 0) if (result ~= 0) then do if (symbol('atemp.sendto') ~= 'VAR') then atemp.sendto = '' atemp.sendto = reqstring('Send to', 'Enter an address the messages should\nbe forwarded to. (required)', addasterix(atemp.sendto), 64, 0) if (atemp.sendto ~= '') then do if (symbol('atemp.textfile') = 'VAR') then do pathpart = getpathpart(atemp.textfile) filepart = getfilepart(atemp.textfile) end else do pathpart = '' filepart = '' end call reqfile('Select text file to attach, cancel for none', pathpart, filepart, '#?') if (result ~= '' & ~(right(result, 1) = '/' | right(result, 1) = ':')) then atemp.textfile = result; else drop atemp.textfile end else aborted = 1 end else aborted = 1 end when atemp.type = 'MAIL' then do call getconference(globalcfg.SYSTEM, 0) if (result ~= 0) then do if (symbol('atemp.subject') ~= 'VAR') then atemp.subject = '' atemp.subject = reqstring('Subject', 'Enter a message subject to beused for the mails. (required)', addasterix(atemp.subject), 256, 0) if (atemp.subject ~= '') then do if (symbol('atemp.textfile') = 'VAR') then do pathpart = getpathpart(atemp.textfile) filepart = getfilepart(atemp.textfile) end else do pathpart = '' filepart = '' end call reqfile('Select text file (required)', pathpart, filepart, '#?') if (result ~= '' & ~(right(result, 1) = '/' | right(result, 1) = ':')) then do atemp.textfile = result if (symbol('atemp.sendto') ~= 'VAR') then atemp.sendto = '' atemp.sendto = reqstring('Send to', 'Enter an address the messages\nshould be mailed to. If you do\nnot specify an address it will\nbe mailed to the sender of the\nmessage that caused the trigger.', addasterix(atemp.sendto), 64, 0) if atemp.sendto = '' then drop atemp.sendto end else aborted = 1 end else aborted = 1 end else aborted = 1 end when atemp.type = 'EXTERNAL' then do if symbol('atemp.scriptname') ~= 'VAR' then atemp.scriptname = '' filepart = getfilepart(atemp.scriptname) pathpart = getpathpart(atemp.scriptname); if pathpart = '' then pathpart = thorpath'rexx' call reqfile('Select script', pathpart, filepart, '(#?.br|#?.thor|#?.rexx)') if (result ~= '') then do atemp.scriptname = result if symbol('atemp.scriptopts') ~= 'VAR' then atemp.scriptopts = '' atemp.scriptopts = reqstring('Script options', 'Enter the command line options that should be\npassed to the script. The following keywords\ncan be used:\n\n %%s returns the system name\n %%c returns the email conference name\n %%n returns the number of the message\n\nSortMail will add double quotes (") around %%s\nand %%c so don''t do that yourself, but make\nsure you seperate the items with a space.\n\nIf you don''t want to pass any options then\ncancel this requester.', addasterix(atemp.scriptopts), 256, 1) if atemp.scriptopts = '' then drop atemp.scriptopts end else aborted = 1 end otherwise nop end /* Update action stem */ if ~(aborted) then do if atype = 1 then do; temp.action.count = temp.action.count + 1; ano = temp.action.count; end call copystem(11,, ano) end drop atemp. ano pathpart filepart syschoice conflist. menulist. cno mcnt return(0) /**************************************************************************** **************************** Configure search entry *************************** ****************************************************************************/ cfgsearch: interpret 'procedure expose 'globals parse arg stype, sno if stype = 1 then do menulist.1 = 'In from field ' menulist.2 = 'Not in from field' menulist.3 = 'In to field' menulist.4 = 'Not in to field' menulist.5 = 'In subject' menulist.6 = 'Not in subject' menulist.7 = 'In Reply-To' menulist.8 = 'Not in Reply-To' menulist.9 = 'In header' menulist.10 = 'Not in header' menulist.11 = 'In body' menulist.12 = 'Not in body' menulist.count = 12 searchin = showmenu('Select search type', 18) select when searchin = 0 then return(0) when searchin = 1 then do; stemp.type = 'FROM'; stemp.not = 0; end when searchin = 2 then do; stemp.type = 'FROM'; stemp.not = 1; end when searchin = 3 then do; stemp.type = 'TO'; stemp.not = 0; end when searchin = 4 then do; stemp.type = 'TO'; stemp.not = 1; end when searchin = 5 then do; stemp.type = 'SUBJECT'; stemp.not = 0; end when searchin = 6 then do; stemp.type = 'SUBJECT'; stemp.not = 1; end when searchin = 7 then do; stemp.type = 'REPLYTO'; stemp.not = 0; end when searchin = 8 then do; stemp.type = 'REPLYTO'; stemp.not = 1; end when searchin = 9 then do; stemp.type = 'HEADER'; stemp.not = 0; end when searchin = 10 then do; stemp.type = 'HEADER'; stemp.not = 1; end when searchin = 11 then do; stemp.type = 'BODY'; stemp.not = 0; end when searchin = 12 then do; stemp.type = 'BODY'; stemp.not = 1; end otherwise nop end stemp.criteria = '' if stemp.type = 'HEADER' then stemp.keyword = '' drop searchin end if stype = 2 then call copystem(3, sno) select when stemp.type = 'FROM' then where = 'from field' when stemp.type = 'TO' then where = 'to field' when stemp.type = 'SUBJECT' then where = 'subject' when stemp.type = 'REPLYTO' then where = 'reply-to' when stemp.type = 'HEADER' then where = 'header' when stemp.type = 'BODY' then where = 'message body' otherwise stemp.type = '!unknown!' end if (symbol('stemp.pattern') = 'VAR') then newsearch = stemp.pattern else newsearch = stemp.criteria select when stemp.type = 'HEADER' then do stemp.keyword = addasterix(reqstring('Search keyword', 'Enter the first word on the\nheader line you want to search in.', addasterix(stemp.keyword), 64, 1, 'headline')) if (stemp.keyword = '') then signal bailout if (symbol('retfield2') = 'VAR') then newsearch = retfield2 newsearch = reqstring('Search criteria', 'Enter a string or a pattern to\nsearch for in the 'where'.', addasterix(newsearch), 64, 1,, 1) if newsearch = '' then signal bailout if left(newsearch, 1) = '02'x then do stemp.pattern = substr(newsearch, 2); drop stemp.criteria end else do stemp.criteria = substr(newsearch, 2); drop stemp.pattern end drop refield2 end when stemp.type = 'BODY' then do newsearch = reqstring('Search criteria', 'Enter a string or a pattern to\nsearch for in the 'where'.', addasterix(newsearch), 64, 0,, 1) if newsearch = '' then signal bailout if left(newsearch, 1) = '02'x then do stemp.pattern = substr(newsearch, 2); drop stemp.criteria end else do stemp.criteria = substr(newsearch, 2); drop stemp.pattern end drop stemp.keyword end otherwise do newsearch = reqstring('Search criteria', 'Enter a string or a pattern to\nsearch for in the 'where'.', addasterix(newsearch), 64, 1, stemp.TYPE, 1) if newsearch = '' then signal bailout if left(newsearch, 1) = '02'x then do stemp.pattern = substr(newsearch, 2); drop stemp.criteria end else do stemp.criteria = substr(newsearch, 2); drop stemp.pattern end drop stemp.keyword end end /* Update search stem */ if stype = 1 then do; temp.search.count = temp.search.count + 1; sno = temp.search.count; end call copystem(12, , sno) bailout: drop stemp. newsearch return(0) /**************************************************************************** ***************************** Delete action entry ***************************** ****************************************************************************/ delaction: interpret 'procedure expose 'globals parse arg ano if reqnotify('Are you sure you want\nto delete this entry?') then do acnt = 0 do i = 1 to temp.action.count if i ~= ano then do; acnt = acnt + 1; call copystem(21, i, acnt); end end temp.action.count = temp.action.count - 1; i = i - 1 drop acnt temp.action.i. end return(0) /**************************************************************************** ***************************** Delete search entry ***************************** ****************************************************************************/ delsearch: interpret 'procedure expose 'globals parse arg sno if reqnotify('Are you sure you want\nto delete this entry?') then do scnt = 0 do i = 1 to temp.search.count if i ~= sno then do; scnt = scnt + 1; call copystem(22, i, scnt); end end temp.search.count = temp.search.count - 1; i = i - 1 drop scnt temp.search.i. end return(0) /**************************************************************************** ************************* Show menu and wait for input ************************ ****************************************************************************/ showmenu: interpret 'procedure expose 'globals parse arg menutitle, helpnode if helpnode ~= '' & mvhelp then do rcnt = menulist.count rcnt = rcnt + 1; menulist.rcnt = '' rcnt = rcnt + 1; menulist.rcnt = 'HELP' menulist.count = rcnt end doit: address(thorport) 'REQUESTLIST INSTEM 'menulist' TITLE "'menutitle'" SIZEGADGET' if (rc = 5) then i = 0 else if (rc ~= 0) then displayerror(rc, 'REQUESTLIST:\n'THOR.LASTERROR) else do choice = result if choice = '' then signal doit if choice = 'HELP' then do; call help(helpnode); signal doit; end do i = 1 to menulist.count if choice = menulist.i then break end end drop menulist. return(i) /**************************************************************************** ************************* Write configuration to disk ************************* ****************************************************************************/ writecfg: interpret 'procedure expose 'globals CDF_NOT_ON_BBS = '00008000'x /* This conference is not on the bbs. */ CDNT_NONET = 0 /* This conference is a local conference. This is the default values for new conferences. */ CDNT_MAILFOLDER = 3 /* This conference is a virtual mail folder */ cfgopen = open(cf, bbsdata.BBSPATH || cfgfile || '.new', 'W') if cfgopen then do address(thorport) 'OPENPROGRESS TITLE "CfgSortMail 'version'" TOTAL 'trigger.count' PT "Writing configuration..."' if rc ~= 0 then displayerror(rc, 'OPENPROGRESS:\n'THOR.LASTERROR) else progwin = result entry = 'GLOBAL CONFERENCE "'addasterix(globalcfg.CONFERENCE)'"' if symbol('globalcfg.LOCALTO') = 'VAR' then entry = entry' LOCALTO "'addasterix(globalcfg.LOCALTO)'"' call writeln(cf, entry) if rc ~= 0 then call displayerror(30, 'Error while writing new configuration file.\nRestored previous configuration.') call writeln(cf, '') if rc ~= 0 then call displayerror(30, 'Error while writing new configuration file.\nRestored previous configuration.') if trigger.count > 0 then do i = 1 to trigger.count 'UPDATEPROGRESS 'progwin' CURRENT 'i if rc ~= 0 then displayerror(rc, 'UPDATEPROGRESS:\n'THOR.LASTERROR'\nError while writing new configuration file.\nRestored previous configuration.') entry = 'TRIGGER NAME "'addasterix(trigger.i.NAME)'"' if ~((symbol('trigger.i.groups') ~= 'VAR') | (trigger.i.groups = '') | (trigger.i.groups = 'EMail')) then entry = entry' GROUPS "'addasterix(trigger.i.GROUPS)'"' if trigger.i.deluser then entry = entry' DELUSER' if trigger.i.matchall then entry = entry' MATCHALL' if trigger.i.nolocal then entry = entry' NOLOCAL' call writeln(cf, entry) if rc ~= 0 then call displayerror(30, 'Error while writing new configuration file.\nRestored previous configuration.') if trigger.i.search.count > 0 then do j = 1 to trigger.i.search.count entry = 'SEARCH 'trigger.i.search.j.type if (symbol('trigger.i.search.j.not') = 'VAR') & (trigger.i.search.j.not = 1) then entry = entry' NOT' if (symbol('trigger.i.search.j.criteria') = 'VAR') then entry = entry' SUBSTR "'addasterix(trigger.i.search.j.criteria)'"' if (symbol('trigger.i.search.j.pattern') = 'VAR') then entry = entry' PATTERN "'addasterix(trigger.i.search.j.pattern)'"' if (symbol('trigger.i.search.j.keyword') = 'VAR') then entry = entry' KEYWORD "'addasterix(trigger.i.search.j.keyword)'"' call writeln(cf, entry) if rc ~= 0 then call displayerror(30, 'Error while writing new configuration file.\nRestored previous configuration.') end if trigger.i.action.count > 0 then do j = 1 to trigger.i.action.count entry = 'ACTION 'trigger.i.action.j.type if symbol('trigger.i.action.j.destsys') = 'VAR' then entry = entry' DESTSYS "'addasterix(trigger.i.action.j.destsys)'"' if symbol('trigger.i.action.j.destconf') = 'VAR' then entry = entry' DESTCONF "'addasterix(trigger.i.action.j.destconf)'"' if symbol('trigger.i.action.j.replyaddr') = 'VAR' then entry = entry' REPLYADDR "'addasterix(trigger.i.action.j.replyaddr)'"' if symbol('trigger.i.action.j.scriptname') = 'VAR' then entry = entry' SCRIPTNAME "'addasterix(trigger.i.action.j.scriptname)'"' if symbol('trigger.i.action.j.scriptopts') = 'VAR' then entry = entry' SCRIPTOPTS "'addasterix(trigger.i.action.j.scriptopts)'"' if symbol('trigger.i.action.j.filename') = 'VAR' then entry = entry' FILENAME "'addasterix(trigger.i.action.j.filename)'"' if symbol('trigger.i.action.j.directory') = 'VAR' then entry = entry' DIRECTORY "'addasterix(trigger.i.action.j.directory)'"' if symbol('trigger.i.action.j.substitute') = 'VAR' then entry = entry' SUBSTITUTE "'addasterix(trigger.i.action.j.substitute)'"' if symbol('trigger.i.action.j.with') = 'VAR' then entry = entry' WITH "'addasterix(trigger.i.action.j.with)'"' if symbol('trigger.i.action.j.sendto') = 'VAR' then entry = entry' SENDTO "'addasterix(trigger.i.action.j.sendto)'"' if symbol('trigger.i.action.j.subject') = 'VAR' then entry = entry' SUBJECT "'addasterix(trigger.i.action.j.subject)'"' if symbol('trigger.i.action.j.textfile') = 'VAR' then entry = entry' TEXTFILE "'addasterix(trigger.i.action.j.textfile)'"' if trigger.i.action.j.header = 1 then entry = entry' HEADER' if trigger.i.action.j.append = 1 then entry = entry' APPEND' if trigger.i.action.j.nobin = 1 then entry = entry' NOBIN' if trigger.i.action.j.dontadd = 1 then entry = entry' DONTADD' if trigger.i.action.j.checkdupes = 1 then entry = entry' CHECKDUPES' if trigger.i.action.j.nostats = 1 then entry = entry' NOSTATS' call writeln(cf, entry) if rc ~= 0 then call displayerror(30, 'Error while writing new configuration file.\nRestored previous configuration.') /* Create conference and set appropriate flags if necessary */ if (symbol('trigger.i.action.j.destconf') = 'VAR') then do newconf = trigger.i.action.j.destconf if (symbol('trigger.i.action.j.destsys') = 'VAR') then newsys = trigger.i.action.j.destsys else newsys = globalcfg.SYSTEM address(bbsread) 'CONFIGCONF "'addasterix(newsys)'" "'addasterix(newconf)'"' if (rc ~= 0) then call displayerror(rc, 'CONFIGCONF: 'BBSREAD.LASTERROR'\nError while writing new configuration file.\nRestored previous configuration.') 'GETCONFDATA "'addasterix(newsys)'" "'addasterix(newconf)'" STEM 'confdata if (rc ~= 0) then call displayerror(rc, 'GETCONFDATA: 'BBSREAD.LASTERROR'\nError while writing new configuration file.\nRestored previous configuration.') if (confdata.CONFNETTYPE = CDNT_NONET) then do 'CONFIGCONF "'addasterix(newsys)'" "'addasterix(newconf)'" CONFNETTYPE 'CDNT_MAILFOLDER' SET 'c2x(CDF_NOT_ON_BBS) if (rc ~= 0) then call displayerror(rc, 'CONFIGCONF: 'BBSREAD.LASTERROR'\nError while writing new configuration file.\nRestored previous configuration.') end address(thorport) end end call writeln(cf, 'ENDTRIGGER') if (rc ~= 0) then call displayerror(30, 'Error while writing new configuration file.\nRestored previous configuration.') call writeln(cf, '') if (rc ~= 0) then call displayerror(30, 'Error while writing new configuration file.\nRestored previous configuration.') end end call close(cf) if (exists('C:Copy') & exists('C:Delete')) then do address(command) 'Copy "'bbsdata.BBSPATH || cfgfile || '.new" TO "'bbsdata.BBSPATH || cfgfile'" CLONE QUIET' if (rc ~= 0) then displayerror(30, 'Failed to update configuration file.') 'Delete "'bbsdata.BBSPATH || cfgfile || '.new" FORCE QUIET' if (rc ~= 0) then displayerror(30, 'Failed to delete temporary configuration file.') address(thorport) end if symbol('progwin') = 'VAR' then do 'CLOSEPROGRESS REQ 'progwin if (rc ~= 0) then displayerror(rc, 'CLOSEPROGRESS:\n'THOR.LASTERROR) drop progwin end return(0) /**************************************************************************** *********************** Open and read configuration file ********************** ****************************************************************************/ readcfg: interpret 'procedure expose 'globals triggers = 0 cfgopen = open(cf, bbsdata.BBSPATH || cfgfile, 'R') if cfgopen then do cfglength = seek(cf, 0, 'E'); call seek(cf, 0, 'B') address(thorport) 'OPENPROGRESS TITLE "CfgSortMail 'version'" TOTAL 'cfglength' PT "Reading configuration..."' if rc ~= 0 then displayerror(rc, 'OPENPROGRESS:\n'THOR.LASTERROR'\nConfiguration not loaded.') else progwin = result cfgline = 0 address(bbsread) do until seek(cf, 0) = cfglength entry = readln(cf); cfgline = cfgline + 1 if symbol('progwin') = 'VAR' then do address(thorport) 'UPDATEPROGRESS 'progwin' CURRENT 'seek(cf, 0) if rc ~= 0 then displayerror(rc, 'UPDATEPROGRESS:\n'THOR.LASTERROR'\nConfiguration not loaded.') address(bbsread) end select when upper(subword(entry, 1, 1)) = "SYSTEM" then do call reqnotify('Found old 2.x configuration file. SortMail has\nchanged the configuration file format in version\n3.0. A new one will have to be created from scratch.', 'Ok') signal nocfg end when upper(subword(entry, 1, 1)) = "GLOBAL" then do 'READARGS TEMPLATE "SYSTEM/K,CONFERENCE/A,STATISTICS/S,NOWARN/S,LOGINSTATE/S,LOCALTO/K" STEM 'globalcfg' CMDLINE 'subword(entry, 2) if rc ~= 0 then do call displayerror(10, 'Error in 'cfgfile' line 'cfgline':\n'BBSREAD.LASTERROR'\nConfiguration not loaded.') signal nocfg end globalcfg.SYSTEM = cursys.BBSNAME if left(globalcfg.CONFERENCE, 9) = 'GLOBALCFG' then globalcfg.CONFERENCE = 'EMail' end when upper(subword(entry, 1, 1)) = "TRIGGER" then do oldcfg = 0; olddel = 0 triggers = triggers + 1 trigger.triggers.cmdline = subword(entry, 2) trigger.triggers.DELMSG = 0; trigger.triggers.DELUSER = 0; trigger.triggers.MATCHALL = 0; trigger.triggers.NOLOCAL = 0; trigger.triggers.hitcount = 0; trigger.triggers.failcount = 0 'READARGS TEMPLATE "NAME/A,DELMSG/S,DELUSER/S,MATCHALL/S,NOLOCAL/S,GROUPS/K" STEM 'trigger.triggers' CMDLINE 'trigger.triggers.cmdline if rc ~= 0 then do call displayerror(10, 'Error in 'cfgfile' line 'cfgline':\n'BBSREAD.LASTERROR'\nConfiguration only partitially loaded.') signal nocfg end if (symbol('trigger.triggers.groups') ~= 'VAR') then trigger.triggers.groups = 'EMail' if (trigger.triggers.DELMSG) then do; oldcfg = 1; olddel = 1; end do searches = 0; actions = 0 do until upper(subentry) = 'ENDTRIGGER' subentry = readln(cf); cfgline = cfgline + 1 select when upper(subword(subentry, 1, 1)) = 'ACTION' then do actions = actions + 1 trigger.triggers.action.actions.checkdupes = 0; trigger.triggers.action.actions.dontadd = 0; trigger.triggers.action.actions.nostats = 0trigger.triggers.action.actions.header = 0; trigger.triggers.action.actions.append = 0; trigger.triggers.action.actions.nobin = 0; trigger.triggers.action.actions.hitcount = 0; trigger.triggers.action.actions.failcount = 0 'READARGS TEMPLATE "TYPE/A,DESTCONF/K,DESTSYS/K,REPLYADDR/K,SCRIPTNAME/K,SCRIPTOPTS/K,FILENAME/K,DIRECTORY/K,SUBSTITUTE/K,WITH/K,SENDTO/K,TEXTFILE/K,SUBJECT/K,HEADER/S,APPEND/S,NOBIN/S,CHECKDUPES/S,DONTADD/S,NOSTATS/S" STEM 'trigger.triggers.action.actions' CMDLINE 'subword(subentry, 2) if rc ~= 0 then do call displayerror(10, 'Error in 'cfgfile' line 'cfgline':\n'BBSREAD.LASTERROR'\nConfiguration only partitially loaded.') signal nocfg end trigger.triggers.action.actions.type = upper(trigger.triggers.action.actions.type) if (trigger.triggers.action.actions.type = 'MAIL') & ((symbol('trigger.triggers.action.actions.destconf') ~= 'VAR') | (symbol('trigger.triggers.action.actions.subject') ~= 'VAR') | (symbol('trigger.triggers.action.actions.textfile') ~= 'VAR')) then do call displayerror(10, 'Error in 'cfgfile' line 'cfgline':\nMAIL action type incorrectly configured.\nConfiguration only partitially loaded.') signal nocfg end if (trigger.triggers.action.actions.type = 'FORWARD') & ((symbol('trigger.triggers.action.actions.destconf') ~= 'VAR') | (symbol('trigger.triggers.action.actions.sendto') ~= 'VAR')) then do call displayerror(10, 'Error in 'cfgfile' line 'cfgline':\nFORWARD action type incorrectly configured.\nConfiguration only partitially loaded.') signal nocfg end end when upper(subword(subentry, 1, 1)) = 'SEARCH' then do searches = searches + 1; trigger.triggers.search.searches.not = 0 'READARGS TEMPLATE "TYPE/A,CRITERIA/K,SUBSTR/K,KEYWORD/K,PATTERN/K,NOT/S" STEM 'trigger.triggers.search.searches' CMDLINE 'subword(subentry, 2) if rc ~= 0 then do call displayerror(10, 'Error in 'cfgfile' line 'cfgline':\n'BBSREAD.LASTERROR'\nConfiguration only partitially loaded.') signal nocfg end trigger.triggers.search.searches.type = upper(trigger.triggers.search.searches.type) if (symbol('trigger.triggers.search.searches.substr') = 'VAR') then do trigger.triggers.search.searches.criteria = trigger.triggers.search.searches.substr; drop trigger.triggers.search.searches.substr end if ((symbol('trigger.triggers.search.searches.criteria') ~= 'VAR') & (symbol('trigger.triggers.search.searches.pattern') ~= 'VAR') | (symbol('trigger.triggers.search.searches.criteria') = 'VAR') & (symbol('trigger.triggers.search.searches.pattern') = 'VAR')) then do call displayerror(10, 'Error in 'cfgfile' line 'cfgline':\nIllegal use of search parameters.\nConfiguration only partitially loaded.') signal nocfg end if (trigger.triggers.search.searches.type = 'TOADDR') | (trigger.triggers.search.searches.type = 'TONAME') then do; oldcfg = 1; trigger.triggers.search.searches.type = 'TO'; end if (trigger.triggers.search.searches.type = 'FROMADDR') | (trigger.triggers.search.searches.type = 'FROMNAME') then do; oldcfg = 1; trigger.triggers.search.searches.type = 'FROM'; end end when eof(cf) then do call displayerror(10, 'Premature end of configuration file 'cfgfile'\nConfiguration only partitially loaded.') signal nocfg end when upper(subword(subentry, 1, 1)) = 'TRIGGER' then do call displayerror(10, 'TRIGGER did not contain ENDTRIGGER in 'cfgfile' line 'cfgline'\nConfiguration only partitially loaded.') signal nocfg end otherwise nop end end if (oldcfg) then do if (olddel) then do if (trigger.triggers.action.count = 1) & (trigger.triggers.action.1.type = 'COPY') then trigger.triggers.action.1.type = 'MOVE' else do actions = actions + 1 trigger.triggers.action.actions.type = 'KILL' end drop trigger.triggers.delmsg end end end trigger.triggers.action.count = actions trigger.triggers.search.count = searches end otherwise nop end trigger.count = triggers end end else do call reqnotify('Configuration file did not exist or\ncould not be opened.\n\nIf you are using SortMail for the first\ntime, please take a moment to read\nSortMail.guide which you can find in\nthe Docs directory.', 'Ok') signal nocfg end if (symbol('globalcfg.CONFERENCE') ~= 'VAR') then do globalcfg.SYSTEM = cursys.BBSNAME globalcfg.CONFERENCE = 'EMail' end signal fini nocfg: drop globalcfg.; globalcfg.SYSTEM = cursys.BBSNAME; globalcfg.CONFERENCE = 'EMail' drop trigger.; trigger.count = 0 fini: if (cfgopen) then do call close(cf) address(thorport) if symbol('progwin') = 'VAR' then do 'CLOSEPROGRESS REQ 'progwin if rc ~= 0 then displayerror(rc, 'CLOSEPROGRESS:\n'THOR.LASTERROR) drop progwin end end return(0) /**************************************************************************** *********************** Display error and return or quit ********************** ****************************************************************************/ displayerror: interpret 'procedure expose 'globals parse arg returned, errmsg address(thorport) select when returned > 0 & returned < 20 then do 'REQUESTNOTIFY "CfgSortMail returned 'returned':\n'errmsg'" "Continue|Abort"' if result = 0 then signal cleanup end when returned > 19 then do 'REQUESTNOTIFY "CfgSortMail returned 'returned':\n'errmsg'" "Abort"' signal cleanup end otherwise nop end drop THOR.LASTERROR BBSREAD.LASTERROR address(thorport) return(0) /**************************************************************************** ****** Insert asterix (*) before double quotes (") and existing asterixes ***** ****************************************************************************/ addasterix: interpret 'procedure expose 'globals parse arg str if str = '' then return(str) lastfound = 0; found = index(str, '*') do while found > lastfound secondpart = substr(str, found + length('*')) firstpart = substr(str, 1, length(str) - length(substr(str, found))) str = firstpart || '**' || secondpart lastfound = found + length('**') found = index(str, '*', lastfound) end lastfound = 0; found = index(str, '"') do while found > lastfound secondpart = substr(str, found + length('"')) firstpart = substr(str, 1, length(str) - length(substr(str, found))) str = firstpart || '*"' || secondpart lastfound = found + length('*"') found = index(str, '"', lastfound) end lastfound = 0; found = index(str, '=') do while found > lastfound secondpart = substr(str, found + length('=')) firstpart = substr(str, 1, length(str) - length(substr(str, found))) str = firstpart || '*=' || secondpart lastfound = found + length('*=') found = index(str, '*', lastfound) end return(str) /**************************************************************************** ******************** Extract filename from a complete path ******************** ****************************************************************************/ getfilepart: procedure parse arg path filepart = path if (index(path, '/') > 0) & (lastpos('/', path) ~= length(path)) then filepart = substr(path, (lastpos('/', path) + 1)) else (if index(path, ':') > 0) & (lastpos(':', path) ~= length(path)) then filepart = substr(path, (lastpos(':', path) + 1)) return(filepart) /**************************************************************************** ******************** Extract pathname from a complete path ******************** ****************************************************************************/ getpathpart: procedure expose thorpath parse arg path if index(path, '/') > 0 then path = substr(path, 1, lastpos('/', path)) else if index(path, ':') > 0 then path = substr(path, 1, lastpos(':', path)) return(path) /**************************************************************************** ******************** Copy the contents of a stem to another ******************* ****************************************************************************/ copystem: interpret 'procedure expose 'globals parse arg stemtype, fromnr, tonr if stemtype = 1 then do /* Copy contents of original trigger stem to temporary trigger stem Used when entering trigger configuration */ temp.name = trigger.fromnr.name temp.deluser = trigger.fromnr.deluser temp.matchall = trigger.fromnr.matchall temp.nolocal = trigger.fromnr.nolocal temp.groups = trigger.fromnr.groups temp.action.count = trigger.fromnr.action.count temp.search.count = trigger.fromnr.search.count if trigger.fromnr.search.count > 0 then do i = 1 to trigger.fromnr.search.count temp.search.i.type = trigger.fromnr.search.i.type if symbol('trigger.fromnr.search.i.keyword') = 'VAR' then temp.search.i.keyword = trigger.fromnr.search.i.keyword; else drop temp.search.i.keyword if symbol('trigger.fromnr.search.i.pattern') = 'VAR' then temp.search.i.pattern = trigger.fromnr.search.i.pattern; else drop temp.search.i.pattern if symbol('trigger.fromnr.search.i.criteria') = 'VAR' then temp.search.i.criteria = trigger.fromnr.search.i.criteria; else drop temp.search.i.criteria if symbol('trigger.fromnr.search.i.not') = 'VAR' then temp.search.i.not = trigger.fromnr.search.i.not; else drop temp.search.i.not end if trigger.fromnr.action.count > 0 then do i = 1 to trigger.fromnr.action.count temp.action.i.type = trigger.fromnr.action.i.type if symbol('trigger.fromnr.action.i.destconf') = 'VAR' then temp.action.i.destconf = trigger.fromnr.action.i.destconf; else drop temp.action.i.destconf if symbol('trigger.fromnr.action.i.destsys') = 'VAR' then temp.action.i.destsys = trigger.fromnr.action.i.destsys; else drop temp.action.i.destsys if symbol('trigger.fromnr.action.i.replyaddr') = 'VAR' then temp.action.i.replyaddr = trigger.fromnr.action.i.replyaddr; else drop temp.action.i.replyaddr if symbol('trigger.fromnr.action.i.scriptname') = 'VAR' then temp.action.i.scriptname = trigger.fromnr.action.i.scriptname; else drop temp.action.i.scriptname if symbol('trigger.fromnr.action.i.scriptopts') = 'VAR' then temp.action.i.scriptopts = trigger.fromnr.action.i.scriptopts; else drop temp.action.i.scriptopts if symbol('trigger.fromnr.action.i.filename') = 'VAR' then temp.action.i.filename = trigger.fromnr.action.i.filename; else drop temp.action.i.filename if symbol('trigger.fromnr.action.i.directory') = 'VAR' then temp.action.i.directory = trigger.fromnr.action.i.directory; else drop temp.action.i.directory if symbol('trigger.fromnr.action.i.substitute') = 'VAR' then temp.action.i.substitute = trigger.fromnr.action.i.substitute; else drop temp.action.i.substitute if symbol('trigger.fromnr.action.i.with') = 'VAR' then temp.action.i.with = trigger.fromnr.action.i.with; else drop temp.action.i.with if symbol('trigger.fromnr.action.i.header') = 'VAR' then temp.action.i.header = trigger.fromnr.action.i.header; else drop temp.action.i.header if symbol('trigger.fromnr.action.i.append') = 'VAR' then temp.action.i.append = trigger.fromnr.action.i.append; else drop temp.action.i.append if symbol('trigger.fromnr.action.i.nobin') = 'VAR' then temp.action.i.nobin = trigger.fromnr.action.i.nobin; else drop temp.action.i.nobin if symbol('trigger.fromnr.action.i.checkdupes') = 'VAR' then temp.action.i.checkdupes = trigger.fromnr.action.i.checkdupes; else drop temp.action.i.checkdupes if symbol('trigger.fromnr.action.i.dontadd') = 'VAR' then temp.action.i.dontadd = trigger.fromnr.action.i.dontadd ; else drop temp.action.i.dontadd if symbol('trigger.fromnr.action.i.nostats') = 'VAR' then temp.action.i.nostats = trigger.fromnr.action.i.nostats ; else drop temp.action.i.nostats if symbol('trigger.fromnr.action.i.sendto') = 'VAR' then temp.action.i.sendto = trigger.fromnr.action.i.sendto ; else drop temp.action.i.sendto if symbol('trigger.fromnr.action.i.subject') = 'VAR' then temp.action.i.subject = trigger.fromnr.action.i.subject ; else drop temp.action.i.subject if symbol('trigger.fromnr.action.i.textfile') = 'VAR' then temp.action.i.textfile = trigger.fromnr.action.i.textfile ; else drop temp.action.i.textfile end end if stemtype = 2 then do /* Copy contents of action stem in temporary trigger stem to another temporary action stem Used when entering action configuration */ atemp.type = temp.action.fromnr.type if symbol('temp.action.fromnr.destconf') = 'VAR' then atemp.destconf = temp.action.fromnr.destconf; else drop atemp.destconf if symbol('temp.action.fromnr.destsys') = 'VAR' then atemp.destsys = temp.action.fromnr.destsys; else drop atemp.destsys if symbol('temp.action.fromnr.replyaddr') = 'VAR' then atemp.replyaddr = temp.action.fromnr.replyaddr; else drop atemp.replyaddr if symbol('temp.action.fromnr.scriptname') = 'VAR' then atemp.scriptname = temp.action.fromnr.scriptname; else drop atemp.scriptname if symbol('temp.action.fromnr.scriptopts') = 'VAR' then atemp.scriptopts = temp.action.fromnr.scriptopts; else drop atemp.scriptopts if symbol('temp.action.fromnr.filename') = 'VAR' then atemp.filename = temp.action.fromnr.filename; else drop atemp.filename if symbol('temp.action.fromnr.directory') = 'VAR' then atemp.directory = temp.action.fromnr.directory; else drop atemp.directory if symbol('temp.action.fromnr.substitute') = 'VAR' then atemp.substitute = temp.action.fromnr.substitute; else drop atemp.substitute if symbol('temp.action.fromnr.with') = 'VAR' then atemp.with = temp.action.fromnr.with; else drop atemp.with if symbol('temp.action.fromnr.header') = 'VAR' then atemp.header = temp.action.fromnr.header; else drop atemp.header if symbol('temp.action.fromnr.append') = 'VAR' then atemp.append = temp.action.fromnr.append; else drop atemp.append if symbol('temp.action.fromnr.nobin') = 'VAR' then atemp.nobin = temp.action.fromnr.nobin; else drop atemp.nobin if symbol('temp.action.fromnr.checkdupes') = 'VAR' then atemp.checkdupes = temp.action.fromnr.checkdupes; else drop atemp.checkdupes if symbol('temp.action.fromnr.dontadd') = 'VAR' then atemp.dontadd = temp.action.fromnr.dontadd ; else drop atemp.dontadd if symbol('temp.action.fromnr.nostats') = 'VAR' then atemp.nostats = temp.action.fromnr.nostats ; else drop atemp.nostats if symbol('temp.action.fromnr.sendto') = 'VAR' then atemp.sendto = temp.action.fromnr.sendto ; else drop atemp.sendto if symbol('temp.action.fromnr.subject') = 'VAR' then atemp.subject = temp.action.fromnr.subject ; else drop atemp.subject if symbol('temp.action.fromnr.textfile') = 'VAR' then atemp.textfile = temp.action.fromnr.textfile ; else drop atemp.textfile end if stemtype = 3 then do /* Copy contents of search stem in temporary trigger stem to another temporary search stem Used when entering search configuration */ stemp.type = temp.search.fromnr.type if symbol('temp.search.fromnr.keyword') = 'VAR' then stemp.keyword = temp.search.fromnr.keyword; else drop stemp.keyword if symbol('temp.search.fromnr.pattern') = 'VAR' then stemp.pattern = temp.search.fromnr.pattern; else drop stemp.pattern if symbol('temp.search.fromnr.criteria') = 'VAR' then stemp.criteria = temp.search.fromnr.criteria; else drop stemp.criteria if symbol('temp.search.fromnr.not') = 'VAR' then stemp.not = temp.search.fromnr.not; else drop stemp.not end if stemtype = 10 then do /* Copy contents of temporary trigger stem to original trigger stem Used when exiting trigger configuration */ trigger.tonr.name = temp.name trigger.tonr.deluser = temp.deluser trigger.tonr.matchall = temp.matchall trigger.tonr.nolocal = temp.nolocal trigger.tonr.groups = temp.groups trigger.tonr.action.count = temp.action.count trigger.tonr.search.count = temp.search.count if temp.search.count > 0 then do i = 1 to temp.search.count trigger.tonr.search.i.type = temp.search.i.type if symbol('temp.search.i.criteria') = 'VAR' then trigger.tonr.search.i.criteria = temp.search.i.criteria; else drop trigger.tonr.search.i.criteria if symbol('temp.search.i.keyword') = 'VAR' then trigger.tonr.search.i.keyword = temp.search.i.keyword; else drop trigger.tonr.search.i.keyword if symbol('temp.search.i.pattern') = 'VAR' then trigger.tonr.search.i.pattern = temp.search.i.pattern; else drop trigger.tonr.search.i.pattern if symbol('temp.search.i.not') = 'VAR' then trigger.tonr.search.i.not = temp.search.i.not; else drop trigger.tonr.search.i.not end if temp.action.count > 0 then do i = 1 to temp.action.count trigger.tonr.action.i.type = temp.action.i.type if symbol('temp.action.i.destconf') = 'VAR' then trigger.tonr.action.i.destconf = temp.action.i.destconf; else drop trigger.tonr.action.i.destconf if symbol('temp.action.i.destsys') = 'VAR' then trigger.tonr.action.i.destsys = temp.action.i.destsys; else drop trigger.tonr.action.i.destsys if symbol('temp.action.i.replyaddr') = 'VAR' then trigger.tonr.action.i.replyaddr = temp.action.i.replyaddr; else drop trigger.tonr.action.i.replyaddr if symbol('temp.action.i.scriptname') = 'VAR' then trigger.tonr.action.i.scriptname = temp.action.i.scriptname; else drop trigger.tonr.action.i.scriptname if symbol('temp.action.i.scriptopts') = 'VAR' then trigger.tonr.action.i.scriptopts = temp.action.i.scriptopts; else drop trigger.tonr.action.i.scriptopts if symbol('temp.action.i.filename') = 'VAR' then trigger.tonr.action.i.filename = temp.action.i.filename; else drop trigger.tonr.action.i.filename if symbol('temp.action.i.directory') = 'VAR' then trigger.tonr.action.i.directory = temp.action.i.directory; else drop trigger.tonr.action.i.directory if symbol('temp.action.i.substitute') = 'VAR' then trigger.tonr.action.i.substitute = temp.action.i.substitute; else drop trigger.tonr.action.i.substitute if symbol('temp.action.i.with') = 'VAR' then trigger.tonr.action.i.with = temp.action.i.with; else drop trigger.tonr.action.i.with if symbol('temp.action.i.header') = 'VAR' then trigger.tonr.action.i.header = temp.action.i.header; else drop trigger.tonr.action.i.header if symbol('temp.action.i.append') = 'VAR' then trigger.tonr.action.i.append = temp.action.i.append; else drop trigger.tonr.action.i.append if symbol('temp.action.i.nobin') = 'VAR' then trigger.tonr.action.i.nobin = temp.action.i.nobin; else drop trigger.tonr.action.i.nobin if symbol('temp.action.i.checkdupes') = 'VAR' then trigger.tonr.action.i.checkdupes = temp.action.i.checkdupes; else drop trigger.tonr.action.i.checkdupes if symbol('temp.action.i.dontadd') = 'VAR' then trigger.tonr.action.i.dontadd = temp.action.i.dontadd ; else drop trigger.tonr.action.i.dontadd if symbol('temp.action.i.nostats') = 'VAR' then trigger.tonr.action.i.nostats = temp.action.i.nostats ; else drop trigger.tonr.action.i.nostats if symbol('temp.action.i.sendto') = 'VAR' then trigger.tonr.action.i.sendto = temp.action.i.sendto ; else drop trigger.tonr.action.i.sendto if symbol('temp.action.i.subject') = 'VAR' then trigger.tonr.action.i.subject = temp.action.i.subject ; else drop trigger.tonr.action.i.subject if symbol('temp.action.i.textfile') = 'VAR' then trigger.tonr.action.i.textfile = temp.action.i.textfile ; else drop trigger.tonr.action.i.textfile end end if stemtype = 11 then do /* Copy contents of temporary action stem to temporary trigger stem Used when exiting action configuration */ temp.action.tonr.type = atemp.type if symbol('atemp.destconf') = 'VAR' then temp.action.tonr.destconf = atemp.destconf; else drop temp.action.tonr.destconf if symbol('atemp.destsys') = 'VAR' then temp.action.tonr.destsys = atemp.destsys; else drop temp.action.tonr.destsys if symbol('atemp.replyaddr') = 'VAR' then temp.action.tonr.replyaddr = atemp.replyaddr; else drop temp.action.tonr.replyaddr if symbol('atemp.scriptname') = 'VAR' then temp.action.tonr.scriptname = atemp.scriptname; else drop temp.action.tonr.scriptname if symbol('atemp.scriptopts') = 'VAR' then temp.action.tonr.scriptopts = atemp.scriptopts; else drop temp.action.tonr.scriptopts if symbol('atemp.filename') = 'VAR' then temp.action.tonr.filename = atemp.filename; else drop temp.action.tonr.filename if symbol('atemp.directory') = 'VAR' then temp.action.tonr.directory = atemp.directory; else drop temp.action.tonr.directory if symbol('atemp.substitute') = 'VAR' then temp.action.tonr.substitute = atemp.substitute; else drop temp.action.tonr.substitute if symbol('atemp.with') = 'VAR' then temp.action.tonr.with = atemp.with; else drop temp.action.tonr.with if symbol('atemp.header') = 'VAR' then temp.action.tonr.header = atemp.header; else drop temp.action.tonr.header if symbol('atemp.append') = 'VAR' then temp.action.tonr.append = atemp.append; else drop temp.action.tonr.append if symbol('atemp.nobin') = 'VAR' then temp.action.tonr.nobin = atemp.nobin; else drop temp.action.tonr.nobin if symbol('atemp.checkdupes') = 'VAR' then temp.action.tonr.checkdupes = atemp.checkdupes; else drop temp.action.tonr.checkdupes if symbol('atemp.dontadd') = 'VAR' then temp.action.tonr.dontadd = atemp.dontadd ; else drop temp.action.tonr.dontadd if symbol('atemp.nostats') = 'VAR' then temp.action.tonr.nostats = atemp.nostats ; else drop temp.action.tonr.nostats if symbol('atemp.sendto') = 'VAR' then temp.action.tonr.sendto = atemp.sendto ; else drop temp.action.tonr.sendto if symbol('atemp.subject') = 'VAR' then temp.action.tonr.subject = atemp.subject ; else drop temp.action.tonr.subject if symbol('atemp.textfile') = 'VAR' then temp.action.tonr.textfile = atemp.textfile ; else drop temp.action.tonr.textfile end if stemtype = 12 then do /* Copy contents of temporary search stem to temporary trigger stem Used when exiting action configuration */ temp.search.tonr.type = stemp.type if symbol('stemp.keyword') = 'VAR' then temp.search.tonr.keyword = stemp.keyword; else drop temp.search.tonr.keyword if symbol('stemp.pattern') = 'VAR' then temp.search.tonr.pattern = stemp.pattern; else drop temp.search.tonr.pattern if symbol('stemp.criteria') = 'VAR' then temp.search.tonr.criteria = stemp.criteria; else drop temp.search.tonr.criteria if symbol('stemp.not') = 'VAR' then temp.search.tonr.not = stemp.not; else drop temp.search.tonr.not end if stemtype = 20 then do /* Copy contents of original trigger stem to a new stem number Used when deleting of trigger, main menu and when rearranging triggers */ trigger.tonr.name = trigger.fromnr.name trigger.tonr.deluser = trigger.fromnr.deluser trigger.tonr.matchall = trigger.fromnr.matchall trigger.tonr.nolocal = trigger.fromnr.nolocal trigger.tonr.groups = trigger.fromnr.groups trigger.tonr.action.count = trigger.fromnr.action.count trigger.tonr.search.count = trigger.fromnr.search.count if trigger.fromnr.search.count > 0 then do j = 1 to trigger.fromnr.search.count trigger.tonr.search.j.type = trigger.fromnr.search.j.type if symbol('trigger.fromnr.search.j.keyword') = 'VAR' then trigger.tonr.search.j.keyword = trigger.fromnr.search.j.keyword; else drop trigger.tonr.search.j.keyword if symbol('trigger.fromnr.search.j.pattern') = 'VAR' then trigger.tonr.search.j.pattern = trigger.fromnr.search.j.pattern; else drop trigger.tonr.search.j.pattern if symbol('trigger.fromnr.search.j.criteria') = 'VAR' then trigger.tonr.search.j.criteria = trigger.fromnr.search.j.criteria; else drop trigger.tonr.search.j.criteria if symbol('trigger.fromnr.search.j.not') = 'VAR' then trigger.tonr.search.j.not = trigger.fromnr.search.j.not; else drop trigger.tonr.search.j.not end if trigger.fromnr.action.count > 0 then do j = 1 to trigger.fromnr.action.count trigger.tonr.action.j.type = trigger.fromnr.action.j.type if symbol('trigger.fromnr.action.j.destconf') = 'VAR' then trigger.tonr.action.j.destconf = trigger.fromnr.action.j.destconf; else drop trigger.tonr.action.j.destconf if symbol('trigger.fromnr.action.j.destsys') = 'VAR' then trigger.tonr.action.j.destsys = trigger.fromnr.action.j.destsys; else drop trigger.tonr.action.j.destsys if symbol('trigger.fromnr.action.j.replyaddr') = 'VAR' then trigger.tonr.action.j.replyaddr = trigger.fromnr.action.j.replyaddr; else drop trigger.tonr.action.j.replyaddr if symbol('trigger.fromnr.action.j.scriptname') = 'VAR' then trigger.tonr.action.j.scriptname = trigger.fromnr.action.j.scriptname; else drop trigger.tonr.action.j.scriptname if symbol('trigger.fromnr.action.j.scriptopts') = 'VAR' then trigger.tonr.action.j.scriptopts = trigger.fromnr.action.j.scriptopts; else drop trigger.tonr.action.j.scriptopts if symbol('trigger.fromnr.action.j.filename') = 'VAR' then trigger.tonr.action.j.filename = trigger.fromnr.action.j.filename; else drop trigger.tonr.action.j.filename if symbol('trigger.fromnr.action.j.directory') = 'VAR' then trigger.tonr.action.j.directory = trigger.fromnr.action.j.directory; else drop trigger.tonr.action.j.directory if symbol('trigger.fromnr.action.j.substitute') = 'VAR' then trigger.tonr.action.j.substitute = trigger.fromnr.action.j.substitute; else drop trigger.tonr.action.j.substitute if symbol('trigger.fromnr.action.j.with') = 'VAR' then trigger.tonr.action.j.with = trigger.fromnr.action.j.with; else drop trigger.tonr.action.j.with if symbol('trigger.fromnr.action.j.header') = 'VAR' then trigger.tonr.action.j.header = trigger.fromnr.action.j.header; else drop trigger.tonr.action.j.header if symbol('trigger.fromnr.action.j.append') = 'VAR' then trigger.tonr.action.j.append = trigger.fromnr.action.j.append; else drop trigger.tonr.action.j.append if symbol('trigger.fromnr.action.j.nobin') = 'VAR' then trigger.tonr.action.j.nobin = trigger.fromnr.action.j.nobin; else drop trigger.tonr.action.j.nobin if symbol('trigger.fromnr.action.j.checkdupes') = 'VAR' then trigger.tonr.action.j.checkdupes = trigger.fromnr.action.j.checkdupes; else drop trigger.tonr.action.j.checkdupes if symbol('trigger.fromnr.action.j.dontadd') = 'VAR' then trigger.tonr.action.j.dontadd = trigger.fromnr.action.j.dontadd ; else drop trigger.tonr.action.j.dontadd if symbol('trigger.fromnr.action.j.nostats') = 'VAR' then trigger.tonr.action.j.nostats = trigger.fromnr.action.j.nostats ; else drop trigger.tonr.action.j.nostats if symbol('trigger.fromnr.action.j.sendto') = 'VAR' then trigger.tonr.action.j.sendto = trigger.fromnr.action.j.sendto ; else drop trigger.tonr.action.j.sendto if symbol('trigger.fromnr.action.j.subject') = 'VAR' then trigger.tonr.action.j.subject = trigger.fromnr.action.j.subject ; else drop trigger.tonr.action.j.subject if symbol('trigger.fromnr.action.j.textfile') = 'VAR' then trigger.tonr.action.j.textfile = trigger.fromnr.action.j.textfile ; else drop trigger.tonr.action.j.textfile end end if stemtype = 21 then do /* Copy contents of temporary action stem to a new action stem number Used when deleting action stem */ temp.action.tonr.type = temp.action.fromnr.type if symbol('temp.action.fromnr.destconf') = 'VAR' then temp.action.tonr.destconf = temp.action.fromnr.destconf; else drop temp.action.tonr.destconf if symbol('temp.action.fromnr.destsys') = 'VAR' then temp.action.tonr.destsys = temp.action.fromnr.destsys; else drop temp.action.tonr.destsys if symbol('temp.action.fromnr.replyaddr') = 'VAR' then temp.action.tonr.replyaddr = temp.action.fromnr.replyaddr; else drop temp.action.tonr.replyaddr if symbol('temp.action.fromnr.scriptname') = 'VAR' then temp.action.tonr.scriptname = temp.action.fromnr.scriptname; else drop temp.action.tonr.scriptname if symbol('temp.action.fromnr.scriptopts') = 'VAR' then temp.action.tonr.scriptopts = temp.action.fromnr.scriptopts; else drop temp.action.tonr.scriptopts if symbol('temp.action.fromnr.filename') = 'VAR' then temp.action.tonr.filename = temp.action.fromnr.filename; else drop temp.action.tonr.filename if symbol('temp.action.fromnr.directory') = 'VAR' then temp.action.tonr.directory = temp.action.fromnr.directory; else drop temp.action.tonr.directory if symbol('temp.action.fromnr.substitute') = 'VAR' then temp.action.tonr.substitute = temp.action.fromnr.substitute; else drop temp.action.tonr.substitute if symbol('temp.action.fromnr.with') = 'VAR' then temp.action.tonr.with = temp.action.fromnr.with; else drop temp.action.tonr.with if symbol('temp.action.fromnr.header') = 'VAR' then temp.action.tonr.header = temp.action.fromnr.header; else drop temp.action.tonr.header if symbol('temp.action.fromnr.append') = 'VAR' then temp.action.tonr.append = temp.action.fromnr.append; else drop temp.action.tonr.append if symbol('temp.action.fromnr.nobin') = 'VAR' then temp.action.tonr.nobin = temp.action.fromnr.nobin; else drop temp.action.tonr.nobin if symbol('temp.action.fromnr.checkdupes') = 'VAR' then temp.action.tonr.checkdupes = temp.action.fromnr.checkdupes; else drop temp.action.tonr.checkdupes if symbol('temp.action.fromnr.dontadd') = 'VAR' then temp.action.tonr.dontadd = temp.action.fromnr.dontadd ; else drop temp.action.tonr.dontadd if symbol('temp.action.fromnr.nostats') = 'VAR' then temp.action.tonr.nostats = temp.action.fromnr.nostats ; else drop temp.action.tonr.nostats if symbol('temp.action.fromnr.sendto') = 'VAR' then temp.action.tonr.sendto = temp.action.fromnr.sendto ; else drop temp.action.tonr.sendto if symbol('temp.action.fromnr.subject') = 'VAR' then temp.action.tonr.subject = temp.action.fromnr.subject ; else drop temp.action.tonr.subject if symbol('temp.action.fromnr.textfile') = 'VAR' then temp.action.tonr.textfile = temp.action.fromnr.textfile ; else drop temp.action.tonr.textfile end if stemtype = 22 then do /* Copy contents of temporary search stem to a new search stem number Used when deleting search stem */ temp.search.tonr.type = temp.search.fromnr.type if symbol('temp.search.fromnr.keyword') = 'VAR' then temp.search.tonr.keyword = temp.search.fromnr.keyword; else drop temp.search.tonr.keyword if symbol('temp.search.fromnr.pattern') = 'VAR' then temp.search.tonr.pattern = temp.search.fromnr.pattern; else drop temp.search.tonr.pattern if symbol('temp.search.fromnr.criteria') = 'VAR' then temp.search.tonr.criteria = temp.search.fromnr.criteria; else drop temp.search.tonr.criteria if symbol('temp.search.fromnr.not') = 'VAR' then temp.search.tonr.not = temp.search.fromnr.not; else drop temp.search.tonr.not end return(0) /**************************************************************************** *********************** Pop up a notification requester *********************** ****************************************************************************/ reqnotify: interpret 'procedure expose 'globals parse arg body, choices if choices = '' then choices = 'Yes|No' address(thorport) 'REQUESTNOTIFY "'addasterix(body)'" "'choices'"' if rc ~= 0 then call displayerror(rc, 'REQUESTNOTIFY:\n'THOR.LASTERROR) return(result) /**************************************************************************** ************************** Display a string requester ************************* ****************************************************************************/ reqstring: interpret 'procedure expose 'globals parse arg reqtitle, reqbody, reqid, maxchar, reqmode, pickup, pattern orgreqid = reqid if pattern = '' then pattern = 0 showreq: choices = 'Ok' if (pattern) then choices = '_String|_Pattern' if (pickup ~= '') then choices = choices'|Pick_up' choices = choices'|Cancel' if (symbol('msginfo') = 'VAR') then reqid = msginfo address(thorport) 'REQUESTSTRING "'reqtitle'" BODY "'addasterix(reqbody)'" ID "'reqid'" BT "'choices'" MAXCHARS 'maxchar if ((pattern) & ~(thorrc = 3)) | (~(pattern) & ~(thorrc = 2)) then select when (rc = 5 | result = '') & (reqmode = 0 | reqmode = '') then return(orgreqid) when (rc = 5 | result = '') & (reqmode = 1) then return('') when (rc > 5) then call displayerror(rc, 'REQUESTSTRING:\n'THOR.LASTERROR) when (pattern) then do if (thorrc = 1) then return('01'x || result) if (thorrc = 2) then return('02'x || result) end when ~(pattern) then if (thorrc = 1) then return(result) otherwise nop end if (symbol('pickup') = 'VAR') & (pickup ~= '') then do msginfo = readmsg(pickup) if (msginfo = 0) then drop msginfo signal showreq end else return(result) /**************************************************************************** *************************** Display a file requester ************************** ****************************************************************************/ reqfile: interpret 'procedure expose 'globals parse arg reqtitle, pathpart, filepart, reqpattern address(thorport) if (pathpart = '') then do 'GETGLOBALCONFIG STEM 'thorcfg if (rc ~= 0) then call displayerror(rc, 'GETGLOBALCONFIG:\n'THOR.LASTERROR) if (right(thorcfg.SAVEDIR, 1) = '/') | (right(thorcfg.SAVEDIR, 1) = ':') then pathpart = thorcfg.SAVEDIR; else pathpart = thorcfg.SAVEDIR || '/' end 'REQUESTFILE "'reqtitle'" ID "'pathpart'" IF "'filepart'" PAT "'reqpattern'" FP' select when (rc = 5) then return('') when (rc > 5) then call displayerror(rc, 'REQUESTFILE:\n'THOR.LASTERROR) otherwise return(result) end /**************************************************************************** ************** Read data from message currently displayed in Thor ************* ****************************************************************************/ readmsg: interpret 'procedure expose 'globals parse arg field address(thorport) 'CURRENTMSG 'currentmsg if rc ~= 0 then do call reqnotify('CURRENTMSG:\n'THOR.LASTERROR, 'Ok') return(0) end select when (field = 'replyaddr') | (field = 'REPLYTO') then do address(bbsread) 'READBRMESSAGE "'currentmsg.BBSNAME'" "'currentmsg.CONFNAME'" 'currentmsg.MSGNR' TEXTSTEM 'text if rc ~= 0 then call displayerror(rc, 'READBRMESSAGE:\n'BBSREAD.LASTERROR) retfield = value('text.replyaddr') if retfield = upper('text.replyaddr') then do; drop text.; return(0); end end when field = 'headline' then do address(bbsread) 'READBRMESSAGE "'currentmsg.BBSNAME'" "'currentmsg.CONFNAME'" 'currentmsg.MSGNR' TEXTSTEM 'text if rc ~= 0 then call displayerror(rc, 'READBRMESSAGE:\n'BBSREAD.LASTERROR) if (symbol('text.comment.count') ~= 'VAR') | (text.comment.count = 0) then do drop text. call reqnotify('There are no headers in the current message.', 'Ok') return(0) end address(thorport) 'REQUESTLIST INSTEM 'text.comment' TITLE "Select header line" SIZEGADGET' if rc = 5 then do; drop text.; return(0); end if rc > 0 then do; call displayerror(rc, 'REQUESTLIST:\n'THOR.LASTERROR); drop text.; return(0); end retfield = subword(result, 1, 1) retfield2 = subword(result, 2) end when (field = 'TO') | (field = 'FROM') then do address(bbsread) 'READBRMESSAGE "'currentmsg.BBSNAME'" "'currentmsg.CONFNAME'" 'currentmsg.MSGNR' HEADSTEM 'head if rc ~= 0 then call displayerror(rc, 'READBRMESSAGE:\n'BBSREAD.LASTERROR) retfield = value('head.'field'ADDR') if retfield = upper('head.'field'ADDR') then do; drop head.; return(0); end end otherwise do address(bbsread) 'READBRMESSAGE "'currentmsg.BBSNAME'" "'currentmsg.CONFNAME'" 'currentmsg.MSGNR' HEADSTEM 'head if rc ~= 0 then call displayerror(rc, 'READBRMESSAGE:\n'BBSREAD.LASTERROR) retfield = value('head.'field) if retfield = upper('head.'field) then do; drop head.; return(0); end end end drop text. head. return(retfield) /**************************************************************************** ********************** Display help using OS3.1+ MultiView ******************** ****************************************************************************/ help: procedure expose thorport thorpath parse arg node if index(show('P',,), 'MV_SORTMAIL') = 0 then do address(thorport) 'GETGLOBALCONFIG STEM 'globcfg address(command) 'Run >NIL: MultiView FILE 'thorpath'Docs/SortMail.guide PORTNAME MV_SORTMAIL PUBSCREEN 'GLOBCFG.PUBSCREENNAME 'WaitForPort MV_SORTMAIL' if rc ~= 0 then do address(thorport) 'REQUESTNOTIFY "Couldn''t start MultiView." "Sigh"' return(0) end end address(MV_SORTMAIL) 'DOTRIGGERMETHOD CONTENTS' 'DOTRIGGERMETHOD CONTENTS' do i = 1 to node; 'DOTRIGGERMETHOD NEXT'; end address(thorport) return(0) /**************************************************************************** ********************* Display a listview with all systems ********************* ****************************************************************************/ getsystem: intepret 'procedure expose 'globals address(thorport) do i = 1 to bbslist.count; menulist.i = center(i'.', 4) || bbslist.i; end; menulist.count = bbslist.count syschoice = showmenu('Select destination system') return(syschoice) /**************************************************************************** ******************* Display a listview with all conferences ******************* ****************************************************************************/ getconference: interpret 'procedure expose 'globals parse arg system, createconf address(bbsread) 'GETCONFLIST BBSNAME "'system'" STEM 'conflist if (rc > 0) then call displayerror(20, 'GETCONFLIST:\n'BBSREAD.LASTERROR) if (conflist.count = 0) then do reqnotify('No conferences created on this\nsystem. Using EMail as default.', 'Ok') atemp.destconf = 'EMail' return(2) end mcnt = 0 if (createconf = 1) then do mcnt = mcnt + 1; menulist.mcnt = 'Create new conference ' mcnt = mcnt + 1; menulist.mcnt = '' end do i = 1 to conflist.count mcnt = mcnt + 1; menulist.mcnt = center(i'.', 4) || conflist.i end menulist.count = mcnt confchoice = showmenu('Select destination conference') select when ((createconf = 1) & (confchoice = 1)) then atemp.destconf = reqstring('Destination conference', 'Enter new conference name.\nThe new conference will be created\nby SortMail when the first message\nis copied to it.', addasterix(atemp.destconf), 64) when ((createconf = 1) & (confchoice > 2)) then do; cno = confchoice - 2; atemp.destconf = conflist.cno; end when ((createconf = 0) & (confchoice > 0)) then atemp.destconf = conflist.confchoice otherwise nop end return(confchoice)