善意提醒

如果您打开本站很慢,布局排版混乱,并且看不到图片,那么可能是因为您还没有掌握用科学的方法上网的本领。
显示标签为“Oracle”的博文。显示所有博文
显示标签为“Oracle”的博文。显示所有博文

2008-04-29

在 Windows 服务中以 SYSDBA 无需密码登录本地 Oracle

因为客户的特殊需求,导致我们要在一个 Windows 服务中以 SYSDBA 权限去访问本地的 Oracle 数据库,并且不能输入任何密码。通常情况下,当 Windows 的用户帐号是 ORA_DBA 组成员时,直接用 sqlplus "/ as sysdba" 的方式就可以登录本地 Oracle 数据库,无需输入 sys 的用户名和密码。因此我计划用这种方式来实现无密码登录 Oracle。但是得到了如下的错误信息:
ORA-01031: insufficient privileges
很明显,权限方面出了问题。想想也应该,我们的 Windows 服务使用的应该是 NT AUTHORITY/SYSTEM(S-1-5-18) 的权限在运行。于是方案 A 出台了。

方案 A:将 SYSTEM 帐户加入 ORA_DBA 组。

从 UI 是看不到 SYSTEM 帐户的,只有用命令行的方式加。
net localgroup ORA_DBA system /add
结果还是得到了 ORA-01031。分析了一下,有可能是因为操作系统认证需要从操作系统的用户帐号来进行,而 SYSTEM 帐号是个内置帐号,在用户管理里面是看不到的。于是这个方案宣告失败。

方案 B:切换到别的帐户运行。
基本上,方案 A 走不通的话,那就只有这种办法了。首先是建立一个 Windows 帐户,并加入 ORA_DBA 组中。密码自己随便定一个就好。
net user dbauser 1qaz2wsx /add
net localgroup ORA_DBA dbauser /add
但是具体怎么切换呢?

方案 B.1:用 runas 切换帐户。
一般 RunAs 服务(也就是 Secondary Logon)都是启动的,所以首先想到用 Windows 2000 / XP / 2003 自带的命令 runas 来实现。
runas /user:dbauser "sqlplus \"/ as sysdba\" ……"
没报错,可是没反应。噢,麻烦来了。这个 runas 命令是要问密码的。
上 Google 找了一下解决方案。试了一下用 vbs 的 SendKeys 那种,手动状态下似乎勉强可行,但要放在 Windows 服务中,没戏。因为它毕竟是模拟客户端的输入来键入密码的。有个自称可以让 runas 支持管道输入密码的小工具 sanur 也没戏,感觉它俩的原理是类似的。

方案 B.2:用第三方工具替代 runas 切换帐户。
既然 runas 这条路走不通,就彻底抛开它另寻出路了。
有个工具 lsrunas 自称可以替代 runas,于是下载下来一试。虽然参数比较罗嗦,但的确是可以替代。不过这个替代也仅限于通常情况下,一旦放到我们的 Windows 服务中,就没有反应了。
另外有人推荐了一个 cpau。这个工具还是不行,不过它倒是留下了一个提示信息,这个信息成了重要的线索:
ERROR: CPAU doesn't support running from LocalSystem.
看来还是 SYSTEM 的权限问题。

方案 B.3:自写工具切换帐户。
看来这些工具都没问题,但是应该都不支持在 SYSTEM 的权限下面切换用户。听说以前某个同事也为这个问题研究了好些天,最后还是放弃了。老实说,听到这个消息我有一点点绝望感。
这 SYSTEM 权限吧,说它低,却能操作很多系统级别的东西。可要说它高呢,有些本来很容易的事情它又做不了。不过理论上通常还是认为 SYSTEM 的权限比较「高」,所以很多黑客都以获得 SYSTEM 权限为目标在半肉鸡上奋斗着。于是,我以 SYSTEM 降权为主要话题,去黑客站点看了看。
还真是有收获,得到了一段代码,是一个利用 API 自己实现的 runas 工具程序。在命令行下使用了一下,感觉挺好,比 Windows 那个 runas 好用多了。
不过,放进 Windows 服务里面,还是不行。真的是快绝望了。

方案 B.4:用 API 直接切换帐户。
由于我们的系统有现成的接口用来启动一个 PipeCmd,所以到目前为止我都是用它去运行了一个批处理,然后在批处理中去做上述操作。该接口其实是用 CreateProcess 实现,因此新建的进程就继承了父进程的权限,也就是 SYSTEM。
但,在那段黑客代码中,切换帐户其实是使用了 CreateProcessWithLogonW 来完成。估计各个工具都类似,因此应该可以跳过中间的多余步骤,用 API 切换帐户来直接运行 sqlplus。
经过多次实验,总算是成功了:
HANDLE hToken;
LogonUser("dbauser", NULL, "1qaz2wsx", LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &hToken);
STARTUPINFO si;
si.cb = sizeof(STARTUPINFO);
GetStartupInfo(&si);
PROCESS_INFORMATION pi;
CreateProcessAsUser(hToken, NULL, cmdbuf, &sa, NULL, TRUE, NULL, NULL, NULL, &si, &pi);
想偷懒的人还是看看代码稍微理解一下吧,因为我也想偷一下懒,直接 Copy & Paste 可能会有问题的。
另外说明一下,CreateProcessWithLogonW 应该也是可以做到的,但是我一直没能试成功。倒是 CreateProcessAsUser 一次成功,于是就这样用下去了。

本来在这里就应该作结了,不过后面还有一点小插曲:这段代码在 Windows 2000 / XP 上可以运行得很好,但是在 2003 上有问题。在 2003 上用这种方式运行 sqlplus 的时候看起来一切正常,但 sql 的内容没有被执行。最终的解决方案是把 dbauser 也给加到了 Administrators 组里面。
net localgroup Administrators dbauser /add
但是在 XP 下面要这样做的话,就有可能会让 Windows 把 dbauser 当成了主管理员,特别是在某些装好之后默认用 Administrator 用户登录的系统上。这样会给用户带来不小的困扰哦,所以最佳的做法应该是判断一下操作系统的版本再去做相应的处理。

2007-04-16

OTL建立连接时可能会遇到的一个bug

最近因为工作原因,从 OCICPP 改为用 OTL 做 Oracle 开发。初时挺诧异的,怎么只有 .h 没有 .cpp?看过才知道原来一个头文件就全做完了。它是对 OCI 的一个封装,可以用 stream 的方式去操作数据库。用起来还是比较简便,但是感觉封装得多了点。虽然 OCI 那些函数也挺复杂的,但至少觉得一切都在自己掌握之中。OTL 这么一封装了之后,有一些实现细节就不得而知了,这就导致了今天发现的一个 bug。

根据 OTL 关于 otl_connect 的说明,其构造函数之一是可以直接用来连接数据库的。虽然正儿八经做这个事情的是 rlogon(db_string),但是对构造函数 otl_connect(connect_str, ...) 的说明是其等于 otl_connect(void) 再加上一个 rlogon(connect_str)。所以,一般就这样写了:

otl_connect* pdb = new otl_connect("...");

然后把 pdb 存在自己的数据库连接池中。如果连接失败,那么会抛出一个 otl_exception 异常。

可是,今天却遇到意外。连接字符串中,tnsname 写对了,但用户名 / 密码没对。于是测试的时候发现,多次连接之后,数据库那边内存爆掉了,ORACLE.EXE 的线程数也多到疯掉。别的客户端全连接不上了,报 ORA-00020 错误。
process 数量多,这倒也在情理之中。可是看了看 session,发现会话其实很少。猜测是什么东西没有释放掉,不是 connect 就是 cursor。查了一下代码,发现没有什么大问题,connect 都是连接池管理,不会无限多下去的。otl_stream 也都是在栈中声明,花括号之后就自动析构了,cursor 也不应该是问题。郁闷中,发现正常的连接反而不会有泄漏发生,会泄漏的都是连接错误的时候。但是连 tnsname 都不对反而就不会了,估计是因为 OCI 那边根本就无法建立一个连接,也就耗不了资源。这下定位到了,就是这个创建连接的代码上有问题。

这段创建连接的代码是这样写的。注意其中捕捉异常的部分:

otl_connect* pdb = NULL;
try
{
  pdb = new otl_connect("...");
}
catch(otl_exception& e)
{
  ……
  if (NULL != pdb)
    delete pdb;
  pdb = NULL;
}

可以看到,如果连接不成功时没有生成对象,那么应该返回空指针,这样 delete 指令也不会发生。如果有对象生成,那么在异常处理代码中应该已经把这个对象给销毁了,而对象占用的资源应该也已经释放了。但事实就是不同,由于 OTL 在这个构造函数中封装了实现细节,而显然这个实现并不完美,于是有了泄漏。

采用如下的代码,便不存在这个问题了:

otl_connect* pdb = NULL;
try
{
  pdb = new otl_connect();
  pdb->rlogon("...");
}
catch(otl_exception& e)
{
  ……
  if (NULL != pdb)
    delete pdb;
  pdb = NULL;
}

根据 OTL 的说明,这两种建立连接的方法应该没有区别。但事实上 OTL 提供的范例代码中都是采用后一种方法。

追进 OTL 的头文件中去看,应该可以弄明白原委。不过为了完成任务,没有那么多时间了,这个任务只好留到下次再说。大致估计了一下,应该是因为在 new otl_connect(connect_str) 的时候就抛了异常,于是代码跳转到了 catch 处,pdb 根本没得到对象指针的赋值,这样新生成的对象就丢了。建议用 OTL 的各位,在栈中是无所谓啦,但如果要在堆中初始化一个连接,千万不要图省事想用构造函数直接一步到位哦!

后记:
其实,如果构造函数中抛了异常,而对象中存在着自己管理的资源,那么很可能会发生资源泄漏,这对于 C++ 程序员而言几乎可以作为一条准则了。在我后来看到《Effective C++》之后,就知道本文属于其中案例之一。我认为,OTL 就不应该支持在构造函数中进行连接这种方式。你给程序员两条路可走,那每条路都肯定会有人走。如果其中一条有坑,那这条路就没必要对外开放了。

2006-04-18

倦了,累了。

上午去了厦门海景皇冠假日酒店。不是去开房,是去参加 Oracle 的业务战略发布会。刚参加工作的时候,也去参加过一次 IBM 的类似会议,还领了一个包回来背背。这次说实话也是冲着礼品去的,要灌输的那点东东,在我看来已经是老调重弹了。

去得晚了点,还好有同事占座。去的人很多,最后多到会场坐不下。组织者把会议室的隔扇搬走了,这样可以坐得下更多的人,却也让这侧面的阴风吹得我冷飕飕的。吹了一会儿,转了转眼球,发觉有点头痛,我知道我已经有点感冒了。

坚持到了会议结束。领礼品的时候,居然不够发,到我面前的时候已经没有了。主办方说以后再寄给我。我也懒得理会了,直接走到中山路 KFC,休息了一下,给客户打电话联系了下午的行动。还好对方两点半才上班,还可以稍微休息一下。

回到单位,一起开会的同事还没来,看来可能路上跑哪儿偷闲去了。还没到两点半,申请的服务器先到公司了。检查了一下配置,其它都对,就是电源只给配了一个。我明明要的是冗余电源嘛!去问商务,说要加一个电源的话,又得多上一千多块钱。算了,下次找 IBM 下单的时候顺手摸一个吧,这次就算了。

交待系统集成部的同事把 RAID 给装好。我赶去客户那边了。带了一个实习生一起去,计划是让他动手我指导,这样对培养新人比较有利。不过到了之后,一问客户,才发现事情并没有我们想象的那么简单。多花了一些时间,总算是把事情搞定了,超出了预定下班时间十几分钟。在我看来也是常事了。

把笔记本丢回公司,然后打算先把服务器的 Linux 装上,还引来了几个兴冲冲的同事。可惜装了一半才发现 CD2 是坏的,只有明天带自己的盘来重装一次了。公司的烂货 CD-R,不知道在哪里买的便宜货?我都是买原装的 SONY 和 TDK 盘的。

在公司附近吃过晚餐,回家的途中遇到一个明天要结婚的同事。明天据说是个良辰吉日,有两位同事都要结婚呢。不过,我并不打算参加婚礼。以我现在这种心情,参加别人的婚礼并不一定是一件痛快的事情。

到家之后,放下手中物品,突然觉得非常的累,头痛也开始明显起来。草草地脱下了鞋子,躺在床上就睡。再醒来的时候,已经是十点多了。头依然隐隐作痛。勉强起来洗漱之后,接着睡。人还是感觉很想睡,却很难舒舒服服地睡着。在床上翻来覆去,辗转反侧。这种经历对于向来睡眠都非常好的我来说,别提有多难受了。