在当今的IT专业人员词典中,黑客,网络钓鱼,Sarbanes Oxley和HIPAA等术语正变得越来越普遍。 尽管他们很熟悉,但是如果您对当前的IBM i安全性实施方案没有完全的信心,它们可能会使您有些颤抖。 如今,安全性已成为热门话题,因为企业中最重要的资产之一就是存储在数据库中的信息。 因此,建立,实施和维护完善的数据库安全策略必须位于IT部门优先级列表的顶部或附近。 在您需要定期问自己的问题列表中,这些问题是:“您是否正在尽一切可能使数据安全可靠?安全实施中是否存在任何空白?如果是,是否有任何方法可以解决这些空白? ” IBM i平台提供了许多安全性功能,包括确保数据安全的方法。 本文向您介绍了“开放数据库文件”出口点,以及如何使用它来解决IBM i数据库安全性实现中的潜在漏洞。
注:本文先前于2007年以IBM白皮书的形式发布。现在,该文章在IBM i区域中以IBMdeveloperWorks®的形式重新发布,以扩大其了解范围,并提高对许多DB2 i版客户发现的主题和技术的认识。有用。 即使该信息已有多年历史,但细节仍然是相关且准确的。
出口点
如果您是IBM i程序员或管理员,则可能已经意识到,甚至可能已经实现了出口点,以帮助管理和保护IBM i应用程序环境和数据。 出口点定义为系统功能或程序中的特定点,可以将控制权传递给一个或多个指定的出口程序。 使用出口点,您可以编写程序以在每次系统上发生特定事件时执行自定义处理。
QIBM_QDB_Open
在IBM i V5R3中,添加了对QIBM_QDB_OPEN(开放数据库文件出口)的支持。 只要打开系统上的数据库表,此退出点就可以调用程序。 正确设置出口点之后,当系统上的任何作业发出打开物理文件,逻辑文件,SQL表或SQL视图的请求时,将调用出口程序。 无论打开请求的来源如何,都会执行此操作; 其中包括编程接口(例如记录级访问和SQL)以及非编程接口(例如IBM i Navigator,IBM Query / 400,用于i的IBM DB2 Web Query,IBM数据文件实用程序(DFU)甚至是IBM i命令“打开查询文件(OPNQRYF)和显示物理文件成员(DSPPFM)”。当数据库打开并发生调用时,退出程序在发出打开请求的同一作业中运行,并且可以实际设置返回码值拒绝公开请求。
了解数据库打开
当请求完全打开时,出口点将调用出口程序。 完全开放会导致创建开放数据路径(ODP)。 ODP提供了从程序到表的直接路径,因此程序可以发出输入和输出操作来访问表中的数据。 如果使用出口点,那么重要的是要了解何时发生全开。 此事件的发生时间取决于多种条件,包括您请求的数据库访问类型:RLA或SQL。
记录级访问
记录级访问(也称为本机I / O )是高级语言(HLL)程序(例如RPG和COBOL)访问文件内容的传统方式。 如今,许多程序仍使用记录级访问来访问数据。 记录级别的完全打开访问发生在程序发出打开操作或方法时,而不是在实际尝试访问数据的过程中(例如READ或CHAIN操作)。
当RPG程序使用记录级别访问时,表在程序的文件规范部分(F-spec)中定义。 实际发生打开的时间取决于F规范中是否指定了USROPN关键字。 如果不是,则在首次调用该程序时会发生隐式的完全打开。 但是,如果指定了USROPN关键字,则程序必须使用OPEN操作或内置的%OPEN函数显式打开表。 无论哪种情况(隐式或显式打开),RPG程序分配给最后一条记录(LR)指示符的值都指示该程序后续调用的表打开行为。 如果打开了LR,则在程序结束之前关闭表,并在每次后续调用程序时发生完全打开。 但是,如果该程序未打开LR,则所有表都将保持打开状态,以便以后对该程序进行调用,从而使它们免于完全打开的负担。 在这种情况下,ODP保持不变,程序处于所谓的重入模式。
具有记录级别访问权限的另一种打开方式是“共享打开方式”,该技术用于在同一作业或路由步骤中的多个程序之间共享文件的ODP。 一个程序对文件进行完全打开,该作业中的所有其他程序可以对该文件使用相同的ODP,从而节省了系统资源,例如处理器,内存和磁盘。 通过在替代中指定SHARE(* YES)参数或通过在打开文件之前创建文件命令来控制共享打开。 共享打开通常在调用OPNQRYF命令的应用程序中使用。
SQL访问
HLL程序和远程接口[例如开放式数据库连接(ODBC),Java数据库连接(JDBC)和ADO.NET]可以使用SQL访问表。 显然,在游标上使用SQL OPEN语句时会发生打开。 启动SELECT INTO,INSERT,DELETE和UPDATE语句时,打开不是那么明显。 与所有SQL语句一样,这些打开由数据库管理器执行,数据库管理器在内部为应用程序打开表。 在初始执行打开游标或其他SQL语句期间,数据库管理器执行完全打开。 当关闭游标或语句完成运行时,表将关闭。 如果在同一作业或连接中第二次运行同一语句,则将重复该过程并发生另一个完全打开。 第二次执行后,数据库管理器实际上并未关闭该表; 对于同一作业或连接中的语句的后续执行,它保持打开状态。 在语句的第三次执行期间,数据库管理器不必执行完全打开,因为表已经打开。 这称为“伪打开”,此时,该语句处于通常称为“ ODP重用模式”的状态。 与本机I / O操作中的共享打开相比,伪打开的特性和行为相似。
调用出口程序
理解打开类型很重要,因为仅在请求完全打开时才调用出口点程序。 伪打开,共享打开或重入模式下的程序访问表时,不会调用出口程序。
对于定义为多表联接SQL视图,逻辑文件或Query / 400查询,退出程序仅被调用一次,但是其输入参数列表包含正在打开的视图或逻辑文件,以及包含视图或逻辑文件的每个基础表。 退出程序要分别提取和处理每个条目。
另外,许多表(由IBM提供)和临时表从出口点的控制中排除。 因此,即使请求完全打开,如果要打开的表位于QTEMP,QSYS,QSYS2或其他几个系统库中,也不会调用退出程序。 有关这些库的完整列表,请参考IBM i信息中心中的QIBM_QDB_OPEN出口点条目。
用例
与IBM i环境上的多个出口点一样,QIBM_QDB_OPEN的主要实现是补充安全性,特别是数据库安全性。 它可以通过以下方式用于增强现有的安全性实现:
- 协助执行安全策略
- 执行某种级别的审核,以尝试确定存在安全漏洞的位置
实施数据库安全策略
过去,保护数据非常简单。 用户配置文件设置(例如初始程序和职位描述)可能会限制用户只能使用5250个应用程序。 例如,可以将用户配置文件的极限能力参数设置为* YES,以防止用户获得命令行。 可以依靠该应用程序为特定用户封送对特定表的访问。 这种应用程序级别的安全性足以保护数据。 不需要对象级别的安全性,因为没有其他接口可以访问数据。 在当今的环境中,应用程序级别的安全性还不够。 您必须考虑并阻止试图访问主应用程序外部敏感表的用户或进程未经授权访问远程数据库对象的威胁。 外部应用程序和接口,例如IBM i Navigator和其他基于ODBC或JDBC的工具,允许用户访问不受主线应用程序控制或影响的系统的访问权限,因此不受安全性的控制。内置到该应用程序中。
如果应用程序级安全性描述了您的环境,则强烈建议您至少研究实现对象级安全性的选项。 从长远来看,这是保护数据的最佳,最有效的方法之一。 有关对象级安全性的更多信息,请参阅《 IBM i信息中心》和《 IBM i安全性参考》手册。
补充应用程序级安全性
如果您确定实现对象级安全性是一项艰巨的任务,或者只是不存在投资回报(ROI),请考虑使用退出点来帮助您解决安全性方面的一些不足。 对于5250应用程序,您仍然可以依靠应用程序级别的安全性和用户配置文件中的设置来阻止来自基于5250的传统应用程序界面的未经授权的访问。 另外,您可以使用出口点通过上述远程接口来阻止不受限制的访问。
提供安全粒度
解决方案提供商可以使用此出口点(和其他系统出口点)来允许其客户定制,控制和管理本地和远程数据库对象访问策略。 例如,退出程序可能使用一个安全表,其中包含敏感数据库表和授权用户的列表,以及这些用户的本地和远程访问权限(请参阅表1)。
表1:退出程序安全性表的示例
数据库表 | 用户资料 | 本地访问 | 远程访问 |
---|---|---|---|
命令 | 科宝 | ñ | ÿ |
命令 | KMILL | ÿ | ñ |
命令 | 杰瑞克 | ÿ | ÿ |
当数据库开放请求针对表ORDERS调用时,退出程序可以确定所请求的访问类型(本地或远程),在安全性表中查找以验证发出请求的用户是否具有对该表的访问类型,并且因此,允许或拒绝数据库打开请求。
使用表1中的示例数据,用户编写的退出程序执行以下任务:
- 防止用户COBBG从基于5250的主应用程序界面中打开ORDERS表; 但确实允许该用户通过ODBC接口访问ORDERS,以填充报表的电子表格。
- 允许用户KMILL从基于5250的主应用程序中打开ORDERS,但可以阻止该用户使用远程接口访问数据。
- 允许用户JAREK从本地和远程接口打开ORDERS。
您甚至可以更进一步,仅允许在一周中的某些天或一天中的某些小时进行远程访问。 因为您编写出口点程序,所以可以控制对特定用户的访问,这些用户可以在特定条件下访问表。 如您所见,出口点提供了一定程度的安全粒度,这在使用对象级安全性时是不可能的。
稽核
从审计的角度来看,出口点可以帮助您捕获与数据库安全性有关的信息,例如:
- 尝试访问敏感表的用户
- 打开是否是SQL查询的结果
- 打开操作的类型(读取,插入,更新和删除)
- 尝试打开表时使用的程序和接口
比较出口点与对象审核
如果您熟悉IBM i上的安全性功能,那么您可能会知道审计日志,它使您可以指示系统捕获和记录任何与安全性相关的事件的发生。 这些事件记录在称为日志接收器的特殊系统对象中。 审计日志的子集功能之一是能够记录对对象(例如敏感表)的访问。 这称为对象审核 。
例如,要为FLGHT400C模式中的AGENTS表设置对象审计,请从IBM i OS命令行发出以下命令:
- CHGSYSVAL SYSVAL(QAUDCTL)值(* OBJAUD)
- CHGOBJAUD OBJ(FLGHT400C / AGENTS)OBJTYPE(* FILE)OBJAUD(* ALL)
任何访问该表的尝试都会记录到审核日志中。 以后可以使用IBM i OS Display Journal(DSPJRN)或Display Audit Journal Entries(DSPAUDJRNE)命令显示或提取审计信息。
从审计的角度来看,对象审计和出口点的功能似乎相似,但是两者之间存在一些关键差异。
- 管理:使用对象审核,在设置完之后,系统将接管并负责捕获和记录对象的每次访问。 使用出口点,IBM i程序员负责编写程序以提取和记录访问尝试的详细信息,并负责创建日志表和其他支持对象。
- 对象级权限:如果对象级权限阻止发出请求的用户访问表,则不会调用出口点程序。 由于对象访问受到限制,因此不会打开表,也不会调用出口点程序。 这样做的负面影响是,退出程序无法记录此类未经授权的访问尝试的发生。 但是,对象审核可以检测到此类访问请求,即使它们不成功。
- 采取行动的能力:对象审核只能记录对象访问的发生。 它不能阻止访问尝试,也不能立即触发工作流程来向某人警告访问尝试。 只要对象级权限不阻止调用出口点程序,则出口点使程序员能够阻止访问或执行必要的工作流任务。
有关IBM i审计日志和对象审计的更多信息,请参阅《 IBM i信息中心》和《 IBM i安全性》参考手册。
将出口点与数据库触发器进行比较
您可能更熟悉数据库触发器的概念,数据库触发器是与数据库表关联的用户编写的程序或SQL例程。 读取行或表中发生更改时,无论启动更改的接口如何,数据库管理器都会自动激活(触发)数据库触发器。 数据库触发器的主要目的是监视数据库更改并执行与这些更改相关的任务。 在评估是否使用出口点或触发器来满足您的安全性要求时,需要考虑以下几点:
- 管理:出口点是全局(即系统范围)设置。 您只需要在一个地方进行设置并编写退出程序即可。 根据要启用触发器的表的数量,数据库触发器可能需要更多干预。 这是因为您必须为要审核的每个表添加触发器。 此外,为了确保检测到每种类型的数据库访问,必须向每个表添加四个触发器:每个触发器分别用于READ,UPDATE,INSERT和DELETE(尽管每个触发器可以调用相同的程序)。 要执行审核任务,这两种方法都要求您编写程序来创建支持对象。
- 对象级权限:与出口点一样,如果对象级权限阻止用户访问表,则不会调用数据库触发器。
- 性能:由于通常会为要处理的每一行调用触发器,因此就性能而言它们可能会更昂贵。 如果查询处理许多行,则尤其明显。 另外,READ触发器可能很昂贵,必须谨慎使用。 在某些使用READ触发器的情况下,例如查询包含分组字段时,必须在处理该语句之前创建表的临时副本。 如上所述,仅在请求完全打开时才调用出口程序。 与触发器相比,这通常导致更少的程序调用和处理。
- 提供的信息:触发器的优点是,在审核的事件中可以访问实际数据。 在更新的情况下,行的前后图像都提供给触发程序。 但是,出口点程序在完全打开期间被调用,并且不提供有关所访问的行的任何信息。
- 采取行动的能力:与出口点一样,如果对象级权限未阻止用户访问表,则数据库触发器可以阻止访问数据库或执行其他工作流任务的尝试。 为了阻止访问,触发程序仅发出错误信号,以防止请求的操作成功完成。
在决定使用哪种方法时,大部分取决于您的审核要求。 一些审计法规(例如HIPAA)规定,应用程序必须针对敏感数据库和所访问行的内容记录每次访问尝试。 在这种情况下,您实际上别无选择,只能使用触发器来满足您的审核要求。
有关数据库触发器的更多信息,请参阅《 IBM红皮书: 用于iSeries的DB2通用数据库上的存储过程,触发器和用户定义的函数》。
下表总结了每种审核方法。
表2:审计方法的比较
方法 | 行政 | 需要编程 | 被对象级权限禁用 | 性能 | 提供的行内容 | 采取行动的能力 |
---|---|---|---|---|---|---|
出口点 | 系统范围的设置 | 是 | 是 | 仅在完全打开时调用 | 没有 | 是 |
对象级审核 | 系统范围的设置 | 没有 | 没有 | 记录事件 | 没有 | 没有 |
数据库触发器 | 每个表设置 | 是 | 是 | 呼吁每一行 | 是 | 是 |
编写出口点程序
如前所述,在注册出口点之后,它将成为系统范围的设置; 每当系统上的许多数据库表发生完全打开时,都会调用退出程序。 重要的是要了解,发出数据库打开请求的作业在继续打开请求之前会等待退出程序完成。 根据退出程序的编写方式和功能,这可能会对应用程序性能产生深远的负面影响。 需要重复说明的是,不小心实现出口点可能会对应用程序性能产生重大影响。
如果决定使用出口点,则在设计阶段需要注意以下注意事项。
- 确保您的程序可以处理多个表打开的情况(例如在逻辑文件,视图或查询中定义的多个表联接以及在SQL语句中指定的多个表或视图的情况下)。 例如,如果打开了一个多表联接视图,则退出程序仅被调用一次,但是传递给该视图以及构成联接视图的每个表的列表。 为了彻底处理打开请求,您的程序必须能够处理此列表中的每个视图或表。
- 如果退出程序本身正在打开表,请确保检查并处理潜在的递归程序调用; 否则,您的程序将发现自己处于递归无限循环中。 另外,如果退出程序使用记录级访问打开表,则必须在F-spec部分中指定USROPN关键字。 确定导致调用该程序的表不是要在退出程序中打开的表之后,可以在程序中显式打开它。 同样,不执行这些操作会导致递归循环。 本文后面将详细讨论处理递归循环问题的主题。
- 如果退出程序将返回码更改为0,则数据库打开失败。 如果程序未更改返回码或将其更改为1 ,则打开请求将被接受,打开请求将继续。
- 退出程序必须是线程安全的。
一个简单的RPG示例
考虑到这些注意事项,这是一个用RPG编写的退出程序的简单示例。 该程序以包含标题信息的数据结构传递,例如:当前用户,打开的表数,请求的打开类型(输入,输出,更新,删除)以及偏移值。 偏移值包含列表结构的起始地址,以及有关每个已打开表的信息。 该程序执行以下任务:
- 它提取提出数据库打开请求的当前用户的名称。
- 它使用标题结构中的偏移量值来计算表列表的起始地址
- 从表列表中,提取要打开的表。
- 如果要打开的表是FLIGHTS,而请求打开的当前用户是COBBG,则返回码设置为0 ,程序结束。 将返回码设置为0指示数据库管理器阻止打开请求。
- 如果所有表都不是FLIGHTS,则程序将在不更改返回代码的情况下结束。 这允许数据库打开请求继续进行。
示例1:一个简单的RPG程序
h dftactgrp(*no) actgrp(*caller)
*
d DBOP0100 ds qualified
d HeaderSize 10i 0
d FormatName 8
d ArrayOffset 10i 0
d FileCount 10i 0
d ElementLength 10i 0
d JobName 10
d UserName 10
d JobNumber 6
d CurrentUser 10
d QueryOpen 1
d DBOPFile ds qualified based(DBOPFilePtr)
d FileName 10
d Library 10
d Member 10
d 2
d FileType 10i 0
d UnderlyingPF 10i 0
d InputOption 1
d OutputOption 1
d UpdateOption 1
d DeleteOption 1
d returnCode s 10i 0
d i s 10i 0
d userNam s like(dbop0100.UserName)
d fileNam s like(dbopFile.fileName)
d curUser s like(dbop0100.currentUser)
c *entry plist
c parm DBOP0100
c parm ReturnCode
/free
// Extract current user from passed in structure
curUser = dbop0100.CurrentUser;
// Process each table being opened by the request
for i = 1 to DBOP0100.FileCount;
DBOPFilePtr = %addr(DBOP0100) + DBOP0100.ArrayOffset +
(i - 1) * DBOP0100.ElementLength;
FileNam = dbopFile.FileName;
// Don't allow user COBBG to open table FLIGHTS
if curUser = 'COBBG'
and fileNam = 'FLIGHTS';
returnCode = 0;
return;
endif;
endfor;
return;
/end-free
递归注意事项
当一个唯一的目的是阻止某些用户访问特定的表或一组表时,RPG示例(在示例1中)可以很好地工作。 请注意,退出程序本身不执行任何数据库操作。 必须检查的用户配置文件和表在程序中进行了硬编码。 如果此数据需要更动态怎么办? 如果退出程序本身需要从表中读取或向表中插入行(例如,写入审计表时)怎么办? 更改程序以执行此操作需要打开数据库表本身(因此,出口点将发出对出口程序的新调用)。 由于退出堆栈的一个调用已经存在于程序堆栈中,因此会进行递归程序调用; 这是一个问题,因为RPG不支持递归程序调用。
您可能认为可以通过使用ACTGRP(* NEW)参数编译RPG退出程序来解决此问题。 这指示系统每次调用新激活组中的程序。 即使在某些情况下可行,出于性能原因也不建议这样做(创建新的激活组是一项昂贵的系统活动,每次打开数据库都会发生)。 此外,当您指定ACTGRP(* NEW)时,如果由于外部SQL用户定义函数(UDF)的打开表操作而调用了出口点,则出口点将失败。
为避免RPG递归问题,您可以尝试以下选项:
- 对值进行硬编码:如前面的示例所示,如果数据从未更改或很少更改并且不需要审核日志记录,则可以对值进行硬编码。 换句话说,退出程序完全避免访问所有数据库表。 任何修改都需要更新源代码,并通过变更管理系统重新编译和提升对象。
- 使用非数据库对象:与其使用数据库表存储动态数据(例如,将用户配置文件存储在块中,而使用表名进行保护),不如使用数据库对象存储数据。 诸如数据区和用户空间之类的对象对于此目的很有价值。 审核日志记录可以通过其他某种机制来完成,例如将消息发送到消息队列或将条目发送到数据队列。
- 使用其他编程语言:如果出口点程序需要数据库I / O,则建议您使用支持递归的语言[例如C或控制语言(CL)]。 整个程序可以用C编写,也可以编写一个CL程序,该程序向RPG过程发出绑定过程调用(CALLPRC),以执行大部分工作。 尽管您不能递归调用RPG程序,但是可以递归调用RPG过程。 (有关处理递归的C程序和CL程序的示例,请参阅附录 。)
将出口程序添加到出口点
创建退出程序后,您可以指示系统在打开数据库时调用它。 为此,通过执行以下任务,将出口程序添加到QIBM_QDB_OPEN出口点。
- 登录到IBM i 5250会话。
- 在命令行上,键入“使用注册信息(WRKREGINF)”命令,然后按Enter。
- 将显示“使用注册信息”屏幕,其中包含系统出口点列表。 使用此列表可以显示有关出口点和与出口点关联的出口程序的信息。
- 在QIBM_QDB_OPEN出口点旁边输入8,然后按Enter。
在“使用退出程序”屏幕上,指定选项1 ,退出EXITPGM程序和QGPL库(如图1所示),然后按Enter键。
图1:使用“退出程序”屏幕和“添加”选项
弹出“添加退出程序”屏幕,如图2所示。
图2:添加退出程序屏幕

按Enter添加退出程序。 然后将出口程序添加到出口点,如图3所示。
图3:添加退出程序

作为替代方案,将出口程序附加到出口点的快速方法是发出以下IBM i OS添加出口程序(ADDEXITPGM)命令:
ADDEXITPGM EXITPNT(QIBM_QDB_OPEN) FORMAT(DBOP0100) PGMNBR(*LOW) PGM(QGPL/DBOEXIT1)
注意: PGM参数必须使用显式库名称或* CURLIB(作业的当前库)进行限定。
验证出口程序
将出口程序附加到QIBM_QDB_OPEN出口点后,可以通过以下方式验证其有效性。
- 如果退出程序执行任何日志记录,请检查日志表。
- 检查您的作业日志。 如果退出程序由于任何原因而失败(例如找不到该程序,您无权使用该程序或存在用于检入该程序的功能),则消息将保留在作业日志中,但处理将继续。
- 调试作业。 验证程序是否正确的最佳方法可能是启动调试会话,设置断点,打开表(以调用出口点程序)并逐步调试器中的代码。
保护出口程序
编写,设置和验证出口点程序之后,您显然希望保护它。 否则,有可能遭受出口规避。 恶意用户可能会修改或删除出口程序,或者可能会修改出口程序使用的安全表(和其他数据库对象)的内容。 此类操作可能会允许用户访问敏感表,否则出口点程序会拒绝它们。
即使您没有为其他应用程序对象实现对象级安全性,也要强烈考虑使用它来保护出口点程序及其访问的所有对象。 这是退出程序及其关联对象的常规对象级安全建议。
- 使用USRPRF(* OWNER)参数编译退出程序。 当以此方式编译程序时,然后(在运行时)它将采用拥有该程序的用户配置文件的权限。
- 将退出程序的所有者更改为仅对退出程序访问的对象具有足够权限的配置文件。 这是在程序运行时采用其权限的概要文件,并允许正在运行的程序访问具有指定概要文件权限的对象。
- 向出口点程序授予公共* USE权限。 这使用户有权调用程序(但不能修改或删除程序)。
- 向拥有出口程序的用户简要表授予对出口点程序(例如安全表和任何审核表)访问的数据库对象和其他非数据库对象(例如,数据队列和消息队列)的足够权限。 这样可以确保程序具有读取,写入,更新和删除这些对象中的数据所必需的权限。
- 撤消对出口点程序访问的数据库对象和其他非数据库对象的所有* PUBLIC授权。 这样可以防止用户通过出口点程序以外的任何接口访问这些对象。
- 在动态调用中限定库名称。 如果退出程序对其他程序进行任何动态调用,请确保在调用语句中限定库名。 如果将同名程序插入到库列表中比预期程序高的库中,则可以防止该程序被调用。
其他注意事项
您还需要了解有关此出口点的其他注意事项。
- 如果退出程序发出的返回码为0,则数据库打开请求失败,并且作业日志中将显示以下内容:
CPF428E The open failed because exit program DBOEXIT in library DBOEXIT1 associated with exit point QIBM_QDB_OPEN returned a reason code that ended the request.
此外,如果使用SQL访问,则SQL错误消息SQL0953也将显示在作业日志中:
SQL State: 57014 Vendor Code: -952 Message: [SQL0952] Processing of the SQL statement ended. Reason code 11. Cause . . . . . : The SQL operation was ended before normal completion. The reason code is 11.
- 如果查询引擎在其处理过程中创建了临时表,则不会为这些临时表调用退出程序。 对于使用查询引擎的所有接口(SQL,OPNQRYF和Query / 400),都是如此。
- 如果您跟上了SQL查询引擎的最新增强功能,则您可能熟悉实体化查询表(MQT)。 如果是这样,您可能还记得查询优化器可以自由重写SQL查询,并且可以选择使用可用的MQT代替请求的表。 在这种情况下,将为MQT发出打开请求(而不是为MQT所基于的任何表发出请求)。
- 在注册或注销退出程序时,请注意时间。 对于在将退出程序添加到出口点之前启动的任何作业,可能不会调用该退出程序。 相反,如果从出口点删除出口程序,则已经启动的作业可能会继续调用出口程序。
- 必须在系统辅助存储池(ASP)中定义退出程序。
摘要
出口点已经由经验丰富的IBM i程序员使用多年,以帮助管理其应用程序,系统和安全策略。 出口点提供了一种在系统上发生特定情况时调用自定义程序的有效方法。 The exit point QIBM_QDB_OPEN was introduced in IBM i OS V5R3 to assist IBM i programmers and administrators in their quest to tackle various database security issues. This article should have helped you understand what this exit point does and how it can be used to manage database security and enhance your comprehensive IBM i security policies.
附录
The following C and CL program code examples show how to handle the recursion.
Example that handles recursion: C program
It is possible to call C programs recursively. The following example program compares the name of the table being opened to the name of the audit log table DBLOG (to avoid infinite recursion looping). If they are not the same, the program logs the details of the open request to the audit table. The program only logs information for the first table that is passed in and does not perform any type of processing to determine if the user is authorized to open the table.
To create your exit program as described, perform the following steps:
- Create the log table that is to be used for auditing. The log table contains details about each of the database opens that the exit program processes.
- At the IBM i Navigator Run SQL script window, issue the following statements to create the table.
CREATE TABLE QGPL.DBOLOG (QRYOPN CHAR(1), USER CHAR(10), LIBRARY CHAR(10), FILE CHAR(10), JOBNAME CHAR(10), CURNAME CHAR(10), CURDATE CHAR(8), CURTIME CHAR(8));
- Create the C module by issuing the following statements.
CRTSQLCI OBJ(QTEMP/DBOEXIT) SRCFILE(QGPL/QCSRC) SRCMBR(DBOEXIT1) COMMIT(*NONE) DBGVIEW(*SOURCE)
- Create the C program by issuing the following statements.
CRTPGM PGM(QGPL/DBOEXIT1) MODULE(QTEMP/DBOEXIT1) ACTGRP(*CALLER)
Example 2: A sample C program
#include <stdio.h>
#include <string.h>
exec sql include sqlca;
main(int argc, char **argv)
{
int i;
int *returnCode = (int *) argv[2];
struct header
{
int len;
char eyecatcher[8];
int fileOff;
int numFiles;
int fileSpecLen;
char jName[10];
char userName[10];
char jobNumber[6];
char currentName[10];
char isQueryOpen;
char reserved[3];
} *inHdr;
struct fileDef
{
char name[10];
char lib[10];
char mbr[10];
char reserved[2];
int fileType;
int lowerFileType;
char inpOpt;
char outOpt;
char updOpt;
char dltOpt;
char reserved2[4];
} *fileInfo;
char user[11];
char qryopn;
char inputOpt;
char outputOpt;
char updateOpt;
char deleteOpt;
char filename[10];
char library[10];
char jobName[10];
char curName[10];
inHdr = (struct header *) argv[1];
fileInfo = (struct fileDef *) (argv[1] + inHdr->fileOff);
/* If the open is for the SQL table, return to avoid recursion */
/* Also, don't collect information on QSYS and QTCP users */
if (!strncmp(fileInfo->name,"DBOLOG",6) ||
!strncmp(inHdr->userName,"QSYS",4) ||
!strncmp(inHdr->userName,"QTCP",4))
return;
/* Get the user and the type of access */
strncpy(user,inHdr->userName,10);
qryopn = inHdr->isQueryOpen;
/* Get the file and library */
strncpy(filename,fileInfo->name,10);
strncpy(library,fileInfo->lib,10);
inputOpt = fileInfo->inpOpt;
outputOpt = fileInfo->outOpt;
updateOpt = fileInfo->updOpt;
deleteOpt = fileInfo->dltOpt;
/* Get the job and the current name */
strncpy(jobName,inHdr->jName,10);
strncpy(curName,inHdr->currentName,10);
/* Insert the info into the SQL table */
exec sql INSERT INTO qgpl/dbolog VALUES(:qryopn,:user,:library,:filename,
:jobName, :curName, :inputOpt, :outputOpt, :updateOpt, :deleteOpt,
CURRENT DATE, CURRENT TIME);
}
Example that handles recursion: CL program with RPG procedure
If C is not your language of choice, you can still use RPG to perform most of the processing. Because a CL program can be called recursively, an exit program can be written in CL that makes a bound call to an RPG procedure. This works because it is possible to call RPG procedures recursively. For the following examples, a data area, a table, two modules, and one program were created. This exit program performs two primary functions. It prevents any non-application interface from opening the application table. And, it logs the details of the open request to a table for auditing purposes.
To create your exit program as described, perform the following steps.
- Create a data area to hold the data-open exit-point switch . If the switch is on (1), processing continues. If it is off (0), there is no further action (thus, allowing the database open to occur). This added feature, though not necessary, allows you to turn the exit program on and off easily.
CRTDTAARA DTAARA(QGPL/DBOEXITSW) TYPE(*CHAR) LEN(1) TEXT('DB Open exit point switch (1=On,0=Off)')
- Create the table that is used to store the database libraries that need to be secure. The exit program checks all database tables in these libraries for remote access. When the exit program is called, it extracts the library name of the table being opened and looks for a row in this table with a match to that library name. If a matching row is not found, the table being opened does not need to be secured from external access and the program ends immediately. Otherwise, the table does need to be secured and the exit program continues processing. From an IBM i Navigator Run SQL script window, issue the following statement to create the table.
CREATE TABLE QGPL.SECDBLIB (DB_LIB CHAR(10) CCSID 37 DEFAULT NULL);
- Create the table used to store the list of users who have remote access authorization to the database libraries in the SECDBLBFL table. The exit program searches this table for a row that matches both the library of the table being opened and the user who is issuing the open request. If a matching row is found and the remote access flag is set to 1, the user is authorized to access the database table with a remote interface. From an IBM i Navigator Run SQL script window, issue the following statement to create the table.
CREATE TABLE QGPL.AUTUSRFIL(USER_NAME CHAR(10), DB_LIB CHAR(10), LCL_ACS_FL CHAR(1), RMT_ACS_FL CHAR(1));
- Create the log table used for auditing. The log table contains details about each of the database opens that the exit program processes. From an IBM i Navigator Run SQL script window, issue the following statements to create the table.
CREATE TABLE QGPL.DBOLOGFIL (QRYOPN CHAR(1), ACCESSED INTEGER, USER CHAR(10), LIBRARY CHAR(10), FILE CHAR(10), JOBNAME CHAR(10), CURNAME CHAR(10), CURDATE CHAR(8), CURTIME CHAR(8), PGMSTK CHAR(1000));
- Create the CL DBOEXIT1 module. This module first retrieves the data area to check the value of the database-open exit-point switch. If the switch is on, a bound procedure call is issued to the RPG CHKOPEN procedure. This module is also the module that contains the program's entry procedure. Because it is a CL program, it can be called recursively. To create this module, issue the following command from an IBM i OS command line.
CRTCLMOD MODULE(QTEMP/DBOEXIT1) SRCFILE(DBOEXITPT/QCLSRC) DBGVIEW(*ALL)
Example 3: DBOEXIT1 source code (a sample CL program)
PGM PARM(&DBOP0100 &RETURNCODE) DCL VAR(&DBOP0100) TYPE(*CHAR) LEN(2000) DCL VAR(&RETURNCODE) TYPE(*INT) DCL VAR(&LOGFLAG) TYPE(*INT) DCL VAR(&MSG) TYPE(*CHAR) LEN(500) DCL VAR(&DBOEXITSW) TYPE(*CHAR) LEN(1) /* If exit point switch is on, issue bound call to procedure */ RTVDTAARA DTAARA(QGPL/DBOEXITSW) RTNVAR(&DBOEXITSW) IF COND(&DBOEXITSW = '1') THEN(DO) CALLPRC PRC(CHKOPEN) PARM((&DBOP0100) (&RETURNCODE)) ENDDO ENDPGM
- Create the RPG DBOEXIT2 module, which contains the CHKOPN procedures that perform the following tasks.
- It must extract the tables that are to be opened.
- If the table being opened is one of the three tables that the exit program opens, the return code is set to 1 and the exit program ends immediately to avoid a recursion loop.
- If the table is in an application library that is not defined in the SECDBLIBFL table, it is not a database table that needs to be secured. The return code is set to 1 and the exit program ends.
- The module issues an API to retrieve the current job's program stack. Each entry in the program stack is examined. If the program of the stack entry (that is currently being examined) is located in the same library as that of the table to be opened, the open request originated from the application. If none of the program stack entries are in the database library, the open request originated from a remote access request (an interface that is external to the application).
- If the table-open request is issued by an application interface, the return code is set to 1 .
- If all of the following conditions are true, the return code is set to 1 :
- The table-open request is a remote access request.
- The library of the table being opened and the user requesting the open are defined in the AUTUSRFIL table.
- The remote access flag for that row is set to 1 .
- The details of the open request (including the full program stack) are logged to the DBOLOGFIL table for auditing purposes.
- The return code is returned to the caller. If the return code is 1 , the open request is allowed to continue. If the return code is 0 , the open request is rejected.
Note: All I/O processing in this example uses embedded SQL in RPG free-format. This is a new feature in IBM i V5R4. In prior releases, programmers had to end free-format mode to embed SQL statements.
- To create this module, issue the following command at the IBM i OS command line.
CRTSQLRPGI OBJ(QTEMP/DBOEXIT2) SRCFILE(QGPL/QRPGLESRC) COMMIT(*NONE) OBJTYPE(*MODULE)
Example 4: DBOEXIT2 source code (an RPG example)
h nomain //------------------------------------------ // Prototypes //------------------------------------------ d chkOpen pr d dbopParm likeds(dbop0100) d returnCode 10i 0 D rtvPgmStk PR extpgm('QWVRCSTK') D 2000 D 10I 0 D 8 CONST D 56 D 8 CONST D 15 //------------------------------------------ // Data structures //------------------------------------------ d DBOP0100 ds qualified d headerSize 10i 0 d formatName 8 d arrayOffset 10i 0 d fileCount 10i 0 d elementLength 10i 0 d jobName 10 d userName 10 d jobNumber 6 d currentUser 10 d queryOpen 1 //****************************************** p chkOpen b export //****************************************** d chkOpen pi d dbopParm likeds(dbop0100) d returnCode 10i 0 //------------------------------------------ // Data structures //------------------------------------------ d DBOPFile ds qualified based(DBOPFilePtr) d fileName 10 d fileLibrary 10 d member 10 d 2 d fileType 10i 0 d underlyingPF 10i 0 d inputOption 1 d outputOption 1 d updateOption 1 d deleteOption 1 D rcvVar DS 2000 D bytAvl 10I 0 D bytRtn 10I 0 D entries 10I 0 D offset 10I 0 D stkEntryCnt 10I 0 D stkEntThrd 10I 0 D jobIdInf DS D jIDJobNam 10 inz('*') D jIDJUsram 10 inz(*blanks) D jIDJobNbr 6 inz(*blanks) D jIDIntJobID 16 inz(*blanks) D jIDRsrvd 2 inz(*loval) D jIDThrdInd 10I 0 inz(1) D jIDThrdID 8 inz(*loval) D stkEntry DS 256 D stkEntryLen 10I 0 D procOffset 10I 0 Overlay(stkEntry:13) D procLen 10I 0 Overlay(stkEntry:17) D pgmNam 10 Overlay(stkEntry:25) D pgmLib 10 Overlay(stkEntry:35) d fullEntry 1 256 //------------------------------------------ // Standalone variables //------------------------------------------ d qryOpn s like(dbopParm.queryOpen) d userNam s like(dbopParm.UserName) d jobNam s like(dbopParm.jobName) d curUser s like(dbopParm.currentUser) d fileNam s like(dbopFile.fileName) d fileLib s like(dbopFile.fileLibrary) d i s 10i 0 d j s 10i 0 d fullStack s 1000a D rcvVarLen s 10i 0 Inz(%size(rcvVar)) D D apiErr s 15a D procNam s 10a D rmtAcsFlg s 1a D dbLib s 10a /free // Don't perform checks on QSYS and QTCP users curUser= dbopParm.currentUser; if dbopParm.currentUser = 'QSYS' or dbopParm.currentUser = 'QTCP'; return; endif; returnCode = 1; QryOpn = dbopParm.queryOpen; userNam = dbopParm.userName; jobNam = dbopParm.jobName; for i = 1 to dbopParm.fileCount; DBOPFilePtr = %addr(dbopParm) + dbopParm.arrayOffset + (i - 1) * dbopParm.ElementLength; fileNam = dbopFile.fileName; fileLib = dbopFile.fileLibrary; // Avoid an infinite recursion condition by not continuing if the // file being opened is one of the files that this program will be // opening. if fileNam = 'DBOLOGFIL' or fileNam = 'SECDBLIBFL' or fileNam = 'AUTUSRFIL'; returnCode = 1; return; endif; // See if the file being opened is in a library that has // been defined in the table SECDBLIBFL. Entries in this // table are ones that need to be secured from unauthorized // remote access attempts exec sql SELECT db_lib INTO :dbLib FROM dboexit/secDBLibFl WHERE db_lib = :fileLib; // If not not found, we do not need to secure this table. // Execute next iteration to check the next table in the list if %subst(sqlstate:1:2) <> '00'; iter; endif; // At this point, we know the file being opened is one we need // to secure. To determine if the file is being accessed by a // remote interface, we need to examine the entries in the // program stack. // In this case we are going to assume that application programs // are located in the same library as the database files. So // if any of the progam stack entries has a library that matches // the library of the file being opened, we can assume that the // file is being opened via a local, application interface. // Otherwise the inteface is external to the appication, so we // need to continue processing. returnCode = 0; fullStack = *blanks; // Call API to retrieve the program stack rtvPgmStk(rcvVar:rcvVarLen:'CSTK0100':JobIdInf: 'JIDF0100':apiErr); // While checking each pgm stack entry, // save it in varable fullStack so that the // full stack can be included in the log file. for j = 1 to stkEntryCnt; stkEntry = %subst(rcvVar:Offset + 1); procNam = %subst(stkEntry: procOffset + 1: procLen); if procNam <> *blanks; fullStack = %trim(fullStack) + ', ' + %trim(pgmLib) + '/' + %trim(pgmNam) + ':' + procNam; else; fullStack = %trim(fullStack) + ', ' + %trim(pgmLib) + '/' + %trim(pgmNam); endif; // If the program library is same as file lib, this // indicates that the interface opening the file is // part of the secured application. Set return code // to 1 to allow the open request to continue. if pgmLib = fileLib; returnCode = 1; endif; offset = offset + stkEntryLen; endfor; fullStack = 'Stack count is ' + %char(stkEntryCnt) + ': ' + %subst(fullStack : 3 : %len(fullstack)-2); // At this point, if return code is 0, we know that the user // is attempting to access the file via an interface external // to the application. Check the table of authorized users to // determine if this user has remote access authorization if returnCode = 0; exec sql SELECT remote_access_flag INTO :rmtAcsFlg FROM autUsrFil WHERE user_Name = :userNam AND application_library = :fileLib AND remote_access_flag = '1'; // If state is ok, match was found. Set return code to // 1 to indicate that file open request can continue if %subst(sqlstate:1:2) = '00'; returnCode = 1; endif; endif; // Keep an audit log entry of this db open request exec sql INSERT INTO dboLogFil VALUES(:qryOpn, :returnCode, :userNam,:fileLib, :fileNam, :jobNam,:curUser, CURRENT DATE, CURRENT TIME, :fullStack); endfor; return; /end-free p chkOpen e
- After the modules have been created, create the program by issuing the following command.
CRTPGM PGM(DBOEXITPT/DBOEXIT1) MODULE(DBOEXIT1 DBOEXIT2) ENTMOD(DBOEXIT1) ACTGRP(*CALLER)
翻译自: https://www.ibm.com/developerworks/ibmi/library/i-open-db-file-exit-program/index.html