Esempio n. 1
0
def oblive(db, address):
  robot = ftnconfig.robotnames[address[0]]

  addr_id = ftnconfig.get_taddr_id(db, address)
  if addr_id is None:
    raise Exception("not found")

  msg_from = db.prepare("select count (*) from messages where source=$1").first(addr_id)
  msg_to = db.prepare("select count (*) from messages where destination=$1").first(addr_id)
  print (address, addr_id, "from:", msg_from, "to:", msg_to)

  if msg_from!=0 or msg_to!=0:
    print("messages exist")
    return
#  assert( input("enter 'yes' to confirm: ")=="yes" )

  with ftnimport.session(db) as sess:
    for (link_addr,subs_id) in db.prepare("select a.text, s.id from addresses a, subscriptions s where a.id=s.subscriber and s.target=$1")(addr_id):
      print ("unsubscribing",link_addr)
      link_id = ftnconfig.find_link(db, link_addr)
      my_id, pw=ftnaccess.link_password(db, link_id, forrobots=True)
      sess.send_message(ftnconfig.get_taddr(db, my_id), ftnconfig.SYSOP, ("node", link_addr), robot, None, pw, "-"+address[1], sendmode="direct")
      db.prepare("delete from subscriptions where id=$1")(subs_id)
    sess.send_message(("node", ftnconfig.ADDRESS), ftnconfig.SYSOP, ("echo", "FLUID.LOCAL"), "All", None, "removal", address[0]+" "+address[1]+" removed from node")
    db.prepare("delete from deletedvitalsubscriptionwatermarks where target=$1")(addr_id)
    db.prepare("delete from addresses where id=$1")(addr_id)
Esempio n. 2
0
def oblive(db, address):
    robot = ftnconfig.robotnames[address[0]]

    addr_id = ftnconfig.get_taddr_id(db, address)
    if addr_id is None:
        raise Exception("not found")

    msg_from = db.prepare(
        "select count (*) from messages where source=$1").first(addr_id)
    msg_to = db.prepare(
        "select count (*) from messages where destination=$1").first(addr_id)
    print(address, addr_id, "from:", msg_from, "to:", msg_to)

    if msg_from != 0 or msg_to != 0:
        print("messages exist")
        return


#  assert( input("enter 'yes' to confirm: ")=="yes" )

    with ftnimport.session(db) as sess:
        for (link_addr, subs_id) in db.prepare(
                "select a.text, s.id from addresses a, subscriptions s where a.id=s.subscriber and s.target=$1"
        )(addr_id):
            print("unsubscribing", link_addr)
            link_id = ftnconfig.find_link(db, link_addr)
            my_id, pw = ftnaccess.link_password(db, link_id, forrobots=True)
            sess.send_message(ftnconfig.get_taddr(db, my_id),
                              ftnconfig.SYSOP, ("node", link_addr),
                              robot,
                              None,
                              pw,
                              "-" + address[1],
                              sendmode="direct")
            db.prepare("delete from subscriptions where id=$1")(subs_id)
        sess.send_message(("node", ftnconfig.ADDRESS), ftnconfig.SYSOP,
                          ("echo", "FLUID.LOCAL"), "All", None, "removal",
                          address[0] + " " + address[1] + " removed from node")
        db.prepare(
            "delete from deletedvitalsubscriptionwatermarks where target=$1")(
                addr_id)
        db.prepare("delete from addresses where id=$1")(addr_id)
Esempio n. 3
0
try:
  cmd = sys.argv[1]
  domain = sys.argv[2]
  area = sys.argv[3]
  subscriber = sys.argv[4]
except:
  print("usage: ./util_subscription.py add|addvital|remove echo|fileecho AREANAME 2:5020/9999")
  exit()

try:
  robot = ftnconfig.robotnames[domain]
except:
  raise Exception("cannot manage subscriptions in domain", domain)

link_id = ftnconfig.find_link(db, subscriber)
my_id, pw = ftnaccess.link_password(db, link_id, forrobots=True)
my_addr = ftnconfig.get_taddr(db, my_id)

with ftnimport.session(db) as sess:
  if cmd == "add":
    try:
      sess.add_subscription(None, domain, area, subscriber)
    except FTNAlreadySubscribed:
      print ("local subscription exists")
    if subscriber != ftnconfig.ADDRESS:
      sess.send_message(my_addr, ftnconfig.SYSOP, ("node", subscriber), robot, None, pw, "+"+area, sendmode="direct")
    else:
      print ("local subscription")

  elif cmd == "remove":
    sess.remove_subscription(domain, area, subscriber)
Esempio n. 4
0
    domain = sys.argv[2]
    area = sys.argv[3]
    subscriber = sys.argv[4]
except:
    print(
        "usage: ./util_subscription.py add|addvital|remove echo|fileecho AREANAME 2:5020/9999"
    )
    exit()

try:
    robot = ftnconfig.robotnames[domain]
except:
    raise Exception("cannot manage subscriptions in domain", domain)

link_id = ftnconfig.find_link(db, subscriber)
my_id, pw = ftnaccess.link_password(db, link_id, forrobots=True)
my_addr = ftnconfig.get_taddr(db, my_id)

with ftnimport.session(db) as sess:
    if cmd == "add":
        try:
            sess.add_subscription(None, domain, area, subscriber)
        except FTNAlreadySubscribed:
            print("local subscription exists")
        if subscriber != ftnconfig.ADDRESS:
            sess.send_message(my_addr,
                              ftnconfig.SYSOP, ("node", subscriber),
                              robot,
                              None,
                              pw,
                              "+" + area,
Esempio n. 5
0
def import_msg(sess, m, recv_from, bulk):
    # if m has flag ARQ, create a message with audit info and pack to recv_from then save to dsend to recv_from

    if not bulk and ('AuditRequest' in ftn.attr.binary_to_text(m.attr)):
        print("Sending audit reply to", m.orig[0].decode("ascii"),
              ftn.addr.addr2str(m.orig[1]), "via", recv_from)
        if m.orig[0].decode("ascii").lower() in ("areafix", "filefix",
                                                 "allfix"):
            print("ignoring request")
        else:

            # create message to m.source
            # pack into packet to revc_from

            #print(repr(), m.orig, recv_from, m.date.decode("ascii"))
            audit_m = ftn.msg.MSG()
            audit_m.load(
                (b"Audit Tracker", ftn.addr.str2addr(ADDRESS)), m.orig,
                b"Audit tracking responce",
                time.strftime("%d %b %y  %H:%M:%S").encode("ascii"), 0, 0,
                ("\nThis reply confirms that your message is received by node "
                 + ADDRESS + ".\n"
                 "In case of import errors the reply may be sent again when import will be retried. "
                 "If the message must be routed to next FIDO system on the way, the export confirmation will also be sent.\n"
                 "\n"
                 "If you have received this message for any echomail, please be sure that your system does not "
                 "export echomail messages with ARq flag turned on.\n"
                 "\n"
                 "In case of any issues please contact sysop " + SYSOP + " " +
                 ADDRESS + "\n"
                 "\n" + """ === < MESSAGE BEGIN > ===\n""").encode("ascii") +
                m.as_str(shorten=True) + b""" === < MESSAGE END > ===

--- PyFTN
\1Via """ + ADDRESS.encode("ascii") + b""" ImmediateARqReply """ +
                time.asctime().encode("ascii") + b"\n")

            link_id = find_link(sess.db, recv_from)
            link_pkt_format, link_bundle = get_link_packing(sess.db, link_id)
            myaddr_id, pw = ftnaccess.link_password(sess.db, link_id, False)
            myaddr = get_taddr(db, myaddr_id)

            audit_p = ftn.pkt.PKT(format=link_pkt_format)
            audit_p.msg = [audit_m]
            audit_p.password = pw.encode("ascii")
            audit_p.source = ftn.addr.str2addr(myaddr[1])
            audit_p.destination = ftn.addr.str2addr(recv_from)
            audit_p.date = time.localtime()[:6]
            destdir = addrdir(DOUTBOUND, recv_from)
            os.makedirs(destdir, exist_ok=True)
            pkth, pktfn = tempfile.mkstemp(prefix="arq",
                                           suffix='.pkt',
                                           dir=destdir)
            pktf = os.fdopen(pkth, "wb")
            audit_p.save(pktf)
            pktf.close()
            #open("/tmp/arq.msg", "wb").write(audit_m.pack())

    try:
        sess.import_message(m, recv_from, bulk)
    except FTNDupMSGID as e:
        # move duplicate to dup-area
        dupmsgs.write(
            m.pack(), "Duplicate message received from %s" % recv_from + "\n" +
            traceback.format_exc())
        # and continue
    except UnicodeDecodeError as e:
        badmsgs.write(
            m.pack(),
            "Could not recognize charset in message received from %s" %
            recv_from + "\n" + traceback.format_exc())
    except FTNNoMSGID as e:
        badmsgs.write(
            m.pack(), "No MSGID in message received from %s" % recv_from +
            "\n" + traceback.format_exc())
    except FTNNoOrigin as e:
        badmsgs.write(
            m.pack(), "No Origin in message received from %s" % recv_from +
            "\n" + traceback.format_exc())
    except FTNNotSubscribed as e:
        secmsgs.write(
            m.pack(), "Cannot post message received from %s" % recv_from +
            "\n" + traceback.format_exc())
    except postgresql.exceptions.XMLContentError as e:
        badmsgs.write(
            m.pack(),
            "inadequate XML escaping in message received from %s" % recv_from +
            "\n" + traceback.format_exc())
    except FTNExcessiveMessageSize as e:
        badmsgs.write(
            m.pack(), "Big message received from %s" % recv_from + "\n" +
            traceback.format_exc())
    except FTNFail as e:
        badmsgs.write(
            m.pack(), "Generic FTN fail in message from %s" % recv_from +
            "\n" + traceback.format_exc())
Esempio n. 6
0
def import_msg(sess, m, recv_from, bulk):
  # if m has flag ARQ, create a message with audit info and pack to recv_from then save to dsend to recv_from

  if not bulk and ('AuditRequest' in ftn.attr.binary_to_text(m.attr)):
     print ("Sending audit reply to", m.orig[0].decode("ascii"), ftn.addr.addr2str(m.orig[1]), "via", recv_from)
     if m.orig[0].decode("ascii").lower() in ("areafix","filefix","allfix"):
      print ("ignoring request")
     else:

      # create message to m.source
      # pack into packet to revc_from

      #print(repr(), m.orig, recv_from, m.date.decode("ascii"))
      audit_m = ftn.msg.MSG()
      audit_m.load((b"Audit Tracker", ftn.addr.str2addr(ADDRESS)), m.orig, b"Audit tracking responce",
       time.strftime("%d %b %y  %H:%M:%S").encode("ascii"), 0, 0, 
       ("\nThis reply confirms that your message is received by node "+ADDRESS+".\n"
"In case of import errors the reply may be sent again when import will be retried. "
"If the message must be routed to next FIDO system on the way, the export confirmation will also be sent.\n"
"\n"
"If you have received this message for any echomail, please be sure that your system does not "
"export echomail messages with ARq flag turned on.\n"
"\n"
"In case of any issues please contact sysop "+SYSOP+" "+ADDRESS+"\n"
"\n" +
""" === < MESSAGE BEGIN > ===\n""").encode("ascii") +
m.as_str(shorten=True) +
b""" === < MESSAGE END > ===

--- PyFTN
\1Via """+ADDRESS.encode("ascii")+b""" ImmediateARqReply """+time.asctime().encode("ascii")+b"\n")

      link_id = find_link(sess.db, recv_from)
      link_pkt_format, link_bundle = get_link_packing(sess.db, link_id)
      myaddr_id, pw = ftnaccess.link_password(sess.db, link_id, False)
      myaddr = get_taddr(db, myaddr_id)

      audit_p = ftn.pkt.PKT(format=link_pkt_format)
      audit_p.msg = [audit_m]
      audit_p.password = pw.encode("ascii")
      audit_p.source = ftn.addr.str2addr(myaddr[1])
      audit_p.destination = ftn.addr.str2addr(recv_from)
      audit_p.date = time.localtime()[:6]
      destdir = addrdir(DOUTBOUND, recv_from)
      os.makedirs(destdir, exist_ok = True)
      pkth, pktfn = tempfile.mkstemp(prefix="arq", suffix='.pkt', dir=destdir)
      pktf = os.fdopen(pkth, "wb")
      audit_p.save(pktf)
      pktf.close()
      #open("/tmp/arq.msg", "wb").write(audit_m.pack())


  try:
    sess.import_message(m, recv_from, bulk)
  except FTNDupMSGID as e:
    # move duplicate to dup-area
    dupmsgs.write(m.pack(), "Duplicate message received from %s"%recv_from+"\n"+traceback.format_exc())
    # and continue
  except UnicodeDecodeError as e:
    badmsgs.write(m.pack(), "Could not recognize charset in message received from %s"%recv_from+"\n"+traceback.format_exc())
  except FTNNoMSGID as e:
    badmsgs.write(m.pack(), "No MSGID in message received from %s"%recv_from+"\n"+traceback.format_exc())
  except FTNNoOrigin as e:
    badmsgs.write(m.pack(), "No Origin in message received from %s"%recv_from+"\n"+traceback.format_exc())
  except FTNNotSubscribed as e:
    secmsgs.write(m.pack(), "Cannot post message received from %s"%recv_from+"\n"+traceback.format_exc())
  except postgresql.exceptions.XMLContentError as e:
    badmsgs.write(m.pack(), "inadequate XML escaping in message received from %s"%recv_from+"\n"+traceback.format_exc())
  except FTNExcessiveMessageSize as e:
    badmsgs.write(m.pack(), "Big message received from %s"%recv_from+"\n"+traceback.format_exc())
  except FTNFail as e:
    badmsgs.write(m.pack(), "Generic FTN fail in message from %s"%recv_from+"\n"+traceback.format_exc())
Esempio n. 7
0
def file_export(db, address, password, what):
  """ This generator fetches messages from database and
      yields objects, that contain the file information
      and instructions how to commit to db inforamtion
      about successful message delivery """

    # first netmail
    # then requested file
    # then echoes
    # then filebox
    # and at last fileechoes

  print("export to", repr(address), repr(password), repr(what))

  link_id, addr_id, myaddr_id = ftnaccess.check_link(db, address, password, False)

  if myaddr_id is None:
    raise FTNWrongPassword()

  print("password is correct" if link_id else "unprotected session", "local address", myaddr_id)

  # WARNING!
  # unprotected sessions never must do queries as it may result in leaking netmail
  # if address of some hub is spoofed

  myaddr_text = get_addr(db, myaddr_id)[1]
  link_pkt_format, link_bundler = get_link_packing(db, link_id)
  link_my_id, link_pw = ftnaccess.link_password(db, link_id, False)

  if link_id and ("netmail" in what):
    explock = postgresql.alock.ExclusiveLock(db, ((EXPORTLOCK["netmail"], addr_id)))
    if explock.acquire(False):
      try:

        print ("exporting netmail")
    # only vital subscriptions is processed
    # non-vital (CC) should be processed just like echomail

    # set password in netmail packets
        p = pktpacker(link_pkt_format, myaddr_text, address, link_pw or '', lambda: get_pkt_n(db, link_id), lambda: netmailcommitter())

    #..firstly send pkts in outbound
        for id_msg, src, dest, msgid, header, body, origcharset, recvfrom in get_subscriber_messages_n(db, addr_id, db.FTN_domains["node"]):

          print("netmail %d recvfrom %d pack to %s"%(id_msg, recvfrom, repr(address)))

      # if exporting to utf8z always use UTF-8
          if link_pkt_format == "utf8z":
            origcharset = "utf-8"

          myvia = "PyFTN " + ADDRESS + " " + time.asctime()
          srca=db.prepare("select domain, text from addresses where id=$1").first(src)
          dsta=db.prepare("select domain, text from addresses where id=$1").first(dest)

          try:
            msg, msgcharset = denormalize_message(
            (db.FTN_backdomains[srca[0]], srca[1]),
            (db.FTN_backdomains[dsta[0]], dsta[1]),
            msgid, header, body, origcharset, address, addvia = myvia)
          except:
            raise Exception("denormalization error on message id=%d"%id_msg+"\n"+traceback.format_exc())

          try:
            print ("export msg attributes", msg.attr)
          except:
            traceback.print_exception()

          if 'AuditRequest' in ftn.attr.binary_to_text(msg.attr):
            audit_reply = (db.FTN_backdomains[srca[0]], srca[1]), header.find("sendername").text, address, msg, msgcharset
          else:
            audit_reply = None

          for x in p.add_item(msg, (id_msg, audit_reply)): # add ARQ flag
            yield x

        for x in p.flush():
          yield x

        del p

      finally:
        explock.release()
    else:
      print ("could not acquire netmail lock")

  if "direct" in what: # available for unprotected sessions
    # export messages with processed==8 and destination==addr_id
   explock = postgresql.alock.ExclusiveLock(db, ((EXPORTLOCK["netmail"], addr_id)))
   if explock.acquire(False):

    print ("exporting direct netmail")
    # only vital subscriptions is processed
    # non-vital (CC) should be processed just like echomail

    # set password in netmail packets
    p = pktpacker(link_pkt_format, myaddr_text, address, link_pw or '', lambda: get_pkt_n(db, link_id), lambda: netmailcommitter(newstatus=7))

    #..firstly send pkts in outbound
    for id_msg, src, dest, msgid, header, body, origcharset, recvfrom in get_direct_messages(db, addr_id):

      print("direct netmail %d recvfrom %d pack to %s"%(id_msg, recvfrom, repr(address)))

      # if exporting to utf8z always use UTF-8
      if link_pkt_format == "utf8z":
        origcharset = "utf-8"

      myvia = "PyFTN " + ADDRESS + " DIRECT " + time.asctime()
      srca=db.prepare("select domain, text from addresses where id=$1").first(src)
      dsta=db.prepare("select domain, text from addresses where id=$1").first(dest)

      try:
        msg, msgcharset = denormalize_message(
            (db.FTN_backdomains[srca[0]], srca[1]),
            (db.FTN_backdomains[dsta[0]], dsta[1]), 
            msgid, header, body, origcharset, address, addvia = myvia)
      except:
        raise Exception("denormalization error on message id=%d"%id_msg+"\n"+traceback.format_exc())

      try:
        print ("export msg attributes", msg.attr)
      except:
        traceback.print_exception()

      if 'AuditRequest' in ftn.attr.binary_to_text(msg.attr):
        audit_reply = (db.FTN_backdomains[srca[0]], srca[1]), header.find("sendername").text, address, msg, msgcharset
      else:
        audit_reply = None

      for x in p.add_item(msg, (id_msg, audit_reply)): # add ARQ flag
        yield x

    for x in p.flush():
      yield x

    del p

    explock.release()
    pass

  if link_id and ("echomail" in what):
    explock = postgresql.alock.ExclusiveLock(db, ((EXPORTLOCK["echomail"], addr_id)))
    if explock.acquire(False):
      try:
        print ("exporting echomail")
        #..firstly send bundles in outbound

        #

        if link_bundler:
          p = pktpacker(link_pkt_format, myaddr_text, address, link_pw or '', lambda: get_pkt_n(db, link_id), lambda: echomailcommitter(),
            bundlepacker(link_bundler, address, lambda: get_bundle_n(db, link_id), lambda: echomailcommitter()))
        else:
          p = pktpacker(link_pkt_format, myaddr_text, address, link_pw or '', lambda: get_pkt_n(db, link_id), lambda: echomailcommitter())

        subscache = {}
        for id_msg, xxsrc, dest, msgid, header, body, origcharset, recvfrom, withsubscr, processed in get_subscriber_messages_e(db, addr_id, db.FTN_domains["echo"]):

          # ??? "my" addr in subscription - is it used here

          will_export = True # do we really must send message or just update last_sent pointer

          #print("echomail %d"%id_msg, repr(dest))
          #print("dest %d recvfrom %s subscr %s pack to %s"%(dest, repr(recvfrom), repr(withsubscr), address))
          # ignore src - for echomail it is just recv_from

          # if exporting to utf8z always use UTF-8
          if link_pkt_format == "utf8z":
            origcharset = "utf-8"

          if recvfrom == addr_id:
            #print ("Message from this link, will not export")
            will_export = False

          if processed == 5:
            #print ("Archived message, will not export")
            will_export = False

          # check commuter - NOT TESTED
          subscriber_comm = db.FTN_commuter.get(withsubscr)
          if subscriber_comm is not None: # must check as None==None => no export at all
            # get subscription through what message was received
            recvfrom_subscription = db.prepare("select id from subscriptions where target=$1 and subscriber=$2").first(sub_tart, m_recvfrom)
            recvfrom_comm = db.FTN_commuter.get(recvfrom_subscription)
            if recvfrom_comm == subscriber_comm:
              print("commuter %d - %d, will not export"%(withsubscr, recvfrom_subscription))
              will_export = False
    #          continue # do not forward between subscriptions in one commuter group (e.g. two uplinks)

          if dest in subscache:
            subscribers = subscache[dest]
          else:
            subscribers = db.prepare("select a.domain, a.text from subscriptions s, addresses a where s.target=$1 and s.subscriber=a.id")(dest)

            if not all([x[0]==db.FTN_domains["node"] for x in subscribers]):
              raise FTNFail("subscribers from wrong domain for "+str(sub_targ))

            #    print(sub_id, sub_targ, "all subscribers:", [x[1] for x in subscribers])

            subscribers = subscache[dest] = [x[1] for x in subscribers]

          #print("subscribers:", repr(subscribers))

    #      if withsubscr not in subscribers:
    #        raise Exception("strange: exporting to non-existent subscription", withsubscr)

          dsta = db.prepare("select domain, text from addresses where id=$1").first(dest)

          # modify path and seen-by
          # seen-by's - get list of all subscribers of this target; add subscribers list
          #... if go to another zone remove path and seen-by's and only add seen-by's of that zone -> ftnexport

          if will_export: # create MSG else do not bother
           try:
            msg, msgcharset = denormalize_message( 
                ("node", ADDRESS),
                (db.FTN_backdomains[dsta[0]], dsta[1]), 
                msgid, header, body, origcharset, address, addseenby=subscribers, addpath=ADDRESS)
           except:
            raise Exception("denormalization error on message id=%d"%id_msg+"\n"+traceback.format_exc())

          for x in p.add_item((msg if will_export else None), (withsubscr, id_msg)):
            yield x

        for x in p.flush():
          yield x

      finally:
        explock.release()
    else:
      print("could not acquire echomail lock")

  if link_id and ("filebox" in what):
   explock = postgresql.alock.ExclusiveLock(db, ((EXPORTLOCK["filebox"], addr_id)))
   if explock.acquire(False):
    # ..send freq filebox
    print ("exporting filebox")
    dsend = addrdir(DOUTBOUND, address)
    if os.path.isdir(dsend):
      print ("exporting daemon outbound")
      for f in os.listdir(dsend):
        fname = os.path.join(dsend, f)
        if os.path.isfile(fname):
          obj = outfile()
          obj.data = open(fname, "rb")
          obj.filename = f
          obj.length = os.path.getsize(fname)
          yield obj, filecommitter(fname)

    explock.release()


  if link_id and ("fileecho" in what):
    explock = postgresql.alock.ExclusiveLock(db, ((EXPORTLOCK["fileecho"], addr_id)))
    if explock.acquire(False):
      try:
        print ("exporting fileechoes for", address)
        subscache = {}
        tic_password = password
        t = ticpacker(lambda: get_tic_n(db, link_id), lambda: ticcommitter(db))
        latest_post = db.prepare("select max(post_time) from file_post").first()
        fruitful = False
        for fp_id, fp_filename, fp_destination, fp_recv_from, fp_recv_as, fp_post_time, fp_filedata, \
                fp_origin, fp_other, withsubscr, file_length, file_content, file_lo in \
            db.prepare("select fp.id, fp.filename, fp.destination, fp.recv_from, fp.recv_as, fp.post_time, "
              "fp.filedata, fp.origin, fp.other, s.id, f.length, f.content, f.lo "
            "from file_post fp, subscriptions s, files f "
            "where exists(select * from subscriptions ss where ss.target=fp.destination and ss.subscriber=$1) and s.subscriber=$1 and "
            "fp.post_time>(select lastsent from lastsent where subscriber=$1) and fp.destination=s.target and f.id=fp.filedata "
            "order by fp.post_time")(addr_id):
          print (fp_id, fp_filename, fp_destination)

          will_export = True # do we really must send message or just update last_sent pointer

          if fp_recv_from == addr_id:
            #print ("Message from this link, will not export")
            will_export = False

          other = json.loads(fp_other)
          del fp_other
          seenby = set(other.get("SEENBY", []))

          if address in seenby:
            print ("already in seenby list, not sending")
            will_export = False
          # dupe (own address) in path should be checked at import

          # check commuter - NOT TESTED
          subscriber_comm = db.FTN_commuter.get(withsubscr)
          if subscriber_comm is not None: # must check as None==None => no export at all
            # get subscription through what message was received
            recvfrom_subscription = db.prepare("select id from subscriptions where target=$1 and subscriber=$2").first(sub_tart, m_recvfrom)
            recvfrom_comm = db.FTN_commuter.get(recvfrom_subscription)
            if recvfrom_comm == subscriber_comm:
              print("commuter %d - %d, will not export"%(withsubscr, recvfrom_subscription))
              will_export = False
    #          continue # do not forward between subscriptions in one commuter group (e.g. two uplinks)

          if fp_destination in subscache:
            subscribers = subscache[fp_destination]
          else:
            subscribers = db.prepare("select a.domain, a.text from subscriptions s, addresses a where s.target=$1 and s.subscriber=a.id")(fp_destination)

            if not all([x[0]==db.FTN_domains["node"] for x in subscribers]):
              raise FTNFail("subscribers from wrong domain for "+str(sub_targ))

            #    print(sub_id, sub_targ, "all subscribers:", [x[1] for x in subscribers])

            subscribers = subscache[fp_destination] = [x[1] for x in subscribers]

          dsta = get_addr(db, fp_destination)

          # modify path and seen-by
          # seen-by's - get list of all subscribers of this target; add subscribers list

          if will_export:
            fruitful = True # something was exported

            #print("add seen-by", subscribers, " add path", myaddr_text)

            other.setdefault("PATH", []).append(myaddr_text+" "+str(fp_post_time)+" "+time.asctime(time.gmtime(fp_post_time))+" PyFTN")
            seenby.update(subscribers)
            other["SEENBY"]=list(seenby)

            if "CRC" in other:
              file_crc32 = other.pop("CRC")[0]
              print ("filename %s length %d crc %s"%(fp_filename, file_length, file_crc32))
            else:
              if file_content:
                file_crc32 = ftntic.sz_crc32s(file_content)[1]
              else:
                file_crc32 = ftntic.sz_crc32fd(lo.LOIOReader(db, file_lo))[1]

            #if fp_origin is None:
            #  print("substitute empty origin with source")
            #  fp_origin = fp_recv_from
            tic = ftntic.make_tic(myaddr_text, address, tic_password, dsta[1], get_addr(db, fp_origin)[1] if fp_origin else None,
                fp_filename, file_length, file_crc32, other)

          # code was migrated from echo export, so it cares about blocked exports to update 
          # per-subscription last-sents
          for x in t.add_item(((tic, (fp_filename, file_length, file_content or (db, file_lo))) if will_export else None), (addr_id, fp_post_time)): # ordering by post_time
            yield x

        if not fruitful:
          print("empty export, must update per-subscriber lastsent up to", latest_post)

        for x in t.flush():
          yield x

      finally:
        explock.release()
    else:
      print ("cannot acquire fileecho lock for", addr_id)

  return