C/400

96. API : QSYGETPH 仮想ユーザー・ログインの方法

API: QSYGETPH は IBM API解説書では「プロファイル・ハンドルの取得」
という表題で紹介されているために、うっかり見逃してしまう API であるが、この API によれば
IBM System i の堅牢なユーザー管理システムを利用できる適用業務を構築することができる。
もう少し簡単に説明してみよう。
例えばC/S (クライアント/サーバー) や Web適用業務においてサイン・オンに代わる
ユーザー認証をどのように作成するか? ということである。
ユーザー認証フォーム(HTML)を作成して、エンド・ユーザーにユーザー名やパスワードを
入力させて フォーム SUBMIT したのでは、入力されたユーザーとパスワードは
そのままの形でTCP/IPストリームとしてインターネット回線を流れていくことになり
第三者の傍受によって、いとも簡単にユーザー,パスワードを盗まれてしまうだろう。

このような脆弱なユーザー認証を防ぐにはやはり System i の堅牢なサイン・オンの
システムを利用するに限ることはいうまでも無い。
5250 エミュレータで利用されているサイン・オンによる認証の仕組みは
ここで紹介する API : QSYGETPH によって実現することができる。
つまり QSYGETPH は 5250エミュレータのサインオンの仕組みを再現するための API である。
QSYGETPH とは「プロファイル・ハンドルの取得」という比較的わかりにくい表現で
紹介されているために System i の開発者に利用されている頻度が少ないのではないかと思われる。
「プロファイル・ハンドル」とはユーザー・プロフィールによる環境を構築したときを個別に
識別する識別子のことである。
識別子(ハンドル)とは i5/OS によって重なりのないように自動生成された一意的な値として
生成される。
つまり同じ記号や番号として偶然にも他のものと重なることがないように i5/OS によって
ランダムに発生した記号であり、これによって現在、実行中のユーザー環境を識別するのである。
別の話になるが、あなたも既に自分の Windows PC 上に多くのウィンドウを開いているはずである。
Windows OS は、これらのすべてのウィンドウにやはり「ウィンドウ・ハンドル」と呼ばれる
識別子を持っていて、それらを個別に制御しているのである。
このように複数個、存在する実行中のオブジェクトを識別するための識別子のことを「ハンドル」と呼ぶ。
前置きが長くなってしまったが System i の開発者には「ハンドル」という言葉にはあまり
馴染みがないと思われるので解説した次第である。

仮想ログイン (サイン・オン) の流れ

仮想ログイン(サイン・オン)の流れ

【解説】

仮想ユーザー環境とは QSYGETPH によってハンドルを取得したときに始まって
QSYRLSPH によってハンドルが解放されるまでのあいだの環境のことである。
つまり QSYGETPH によってサイン・オンされて QSYRLSPH によってサイン・オフされると考えてよい。
ここでは API による仮想ユーザー環境の構築と i5/OS リリース別の考慮点についても
現実的で詳細な解説を加えていこうと思う。

QSYGETPH: プロファイル・ハンドルの取得

パラメータ

必須パラメータ・グループ:

1.ユーザーID入力Char(10)
2.パスワード入力Char(*)
3.プロファイル・ハンドル出力Char(12)

任意選択パラメータ:

4.エラー・コード入出力Char(*)

任意選択パラメータ:

5.パスワードの長さ入力Binary(4)
6.パスワードの CCSID入力Binary(4)

1. ユーザーID

ユーザー・ハンドルを生成する、つまり仮想ログインするユーザー・プロフィールの
10桁の名前。特殊値として *CURRENT を指定すると現在のユーザー名のハンドルを
取得することができる。
現在のユーザー(*CURRENT)を取得する理由は、別のユーザーとしてログインしたいときに
現在のユーザーのハンドルを取得しておいてログインしたユーザーを終了したときに
現在のユーザーに戻すために使用する。

2. パスワード

上記で指定するユーザーのパスワード。OS V5R2M0 までは 10桁であるが
V5R3M0 以上では最大128桁までのパスワードを指定することができる。
また特殊値として *NOPWD とはパスワードがないことを意味する。
例えば HTTP サーバーでの省略時のユーザー : QTMHHTTP はパスワードを持たないので
*NOPWD を指定する必要がある。
また QSYS, QDOC, QSPL, QRJE, ... のように、QSYGETPH を使ってもハンドルの取得で
許されていないユーザーも存在する。

3. プロファイル・ハンドル

QSYGETPH によって自動生成される 12 桁のランダム・ストリングとしての
ユーザー環境を識別するための識別子。

5. パスワードの長さ

OS V5R3M0 以降に追加されたパラメータであり、パスワードの長さの指定である。
このパラメータは任意パラメータとして V5R3M0 から初めて紹介されたが
V5R3M0 では必須パラメータであり、正しく指定していないと QSYGETPH は
エラーとなって動作しない。これはOS V5R3M0 のバグであり、とうとう最後まで
IBM によって修正されることはなかった。
弊社が原因不明として苦しめられたバグのひとつである。( V5R3M0 はバグの多い問題のあるリリースであった )
IBM は OS Ver5.4 で、このバグを修正して「任意指定パラメータ」のマニュアルの
とおりに指定していなくてもエラーとはしないように正しい動作に改訂されている。

6. パスワードの CCSID

通常では 65535 を指定しておけばよい。

【 サンプル・ソース:TESTGETPH 】

これはユーザーが任意に入力したユーザーで仮想ログインするプログラムである。

0001.00 #include <stdio.h>                                                          
0002.00 #include <stdlib.h>                                                         
0003.00 #include <string.h>                                                         
0004.00 #include <QSYGETPH.h>                                                       
0005.00 #include <QWTSETP.h>                                                        
0006.00 #include <QSYRLSPH.h>                                                       
0007.00 #include <signal.h>                                                         
0008.00 #include <errno.h>                                                          
0009.00 #include <QMHRTVM.h>                                                        
0010.00 #include <xxdtaa.h>                                                         
0011.00 #include <micomput.h> /* triml */                                           
0012.00                                                                             
0013.00 #define TRUE         0                                                      
0014.00 #define FALSE       -1                                                      
0015.00 typedef struct {                                                            
0016.00    int  BYTESPRO;                                                           
0017.00    int  BYTESAVL;                                                           
0018.00    char MSGID[7];                                                           
0019.00    char RESRVD;                                                             
0020.00    char EXCPDATA[100];                                                      
0021.00 } ERRSTRUCTURE;     /* Define the error return structure            */      
0022.00 ERRSTRUCTURE  errcode;/* Error Code Structure for RCVMSG      */            
0023.00 ERRSTRUCTURE errcd53;                                                       
0024.00    typedef struct {                                                          
0025.00      Qmh_Rtvm_RTVM0100_t  rtvm0100;                                          
0026.00      char msg[512];                                                          
0027.00    } ERRMSG;                                                                 
0028.00    ERRMSG err_msg;                                                           
0029.00 /*************************************************************/              
0030.00 /*       外 部 呼 出 し  関  数                      */              
0031.00 /*************************************************************/              
0032.00 #pragma map(QSYGETPH53, "QSYS/QSYGETPH")                                     
0033.00 #pragma linkage(QSYGETPH53, OS)                                              
0034.00 void QSYGETPH53(char user[10], char* password, char PRFHND[12],              
0035.00      ERRSTRUCTURE* errcode, int passlen, int passCCSID);                     
0036.00                                                                              
0037.00 /*************************************************************/              
0038.00 /*        グ ロ ー バ ル 変 数                         */              
0039.00 /*************************************************************/              
0040.00 char   os400[6];            /* OS/400 バージョン */                          
0041.00   char PRFHND[13];                                                           
0042.00 char   CURHND[13];          /* QTMHHTTP 仮想ログイン・ハンドル */            
0043.00                                                                              
0044.00 void main(void){                                                             
0045.00   int len, pos, passlen, passCCSID = 65535, msglen, msgdtalen;               
0046.00   char user[11], passwrd[49], msgid[8], msg[513], msgdta[133], errmsg[1024]; 
0047.00   char msgtxt[132], ref[133];                                                
0048.00   char* ptr;                                                              
0049.00    _DTAA_NAME_T dtaname = {"QSS1MRI   ", "QGPL      "};                   
0050.00                                                                           
0051.00   printf("** TESTGETPH:QSYGETPH のテスト **\n");                          
0052.00   getchar();                                                              
0053.00   printf("*  ユーザー名を入力してください。 \n");                         
0054.00   scanf("%10s", user);                                                    
0055.00   while(strlen(user) < 10) strcat(user, " ");                             
0056.00   printf("*  パスワードを入力してください。 \n");                         
0057.00   scanf("%10s", passwrd);                                                 
0058.00   passlen = strlen(passwrd);                                              
0059.00   printf("user = [%s], passwrd = [%s]\n", user, passwrd);                 
0060.00                                                                           
0061.00   QXXRTVDA(dtaname, 1, 6, os400);                                         
0062.00   errcd53.BYTESPRO = 160;                                                 
0063.00   errcd53.BYTESAVL = 0;                                                   
0064.00   memset(PRFHND, 0, sizeof(PRFHND));                                      
0065.00   if(strncmp(os400, "V5R3M0", 6) >= 0){/*V5R3M0*/                         
0066.00     pos = triml(passwrd, ' ');                                            
0067.00     if(pos > 0) passwrd[pos] = 0x00;                                      
0068.00     passlen = strlen(passwrd);                                            
0069.00     passCCSID = 65535;                                                    
0070.00     QSYGETPH53(user, passwrd, PRFHND, &errcd53, passlen, passCCSID);      
0071.00   }/*V5R3M0*/                                                             
0072.00   else{/*CISC-V5R2M0*/                                                         
0073.00      QSYGETPH(user, passwrd, PRFHND, &errcd53);                                
0074.00   }/*CISC-V5R2M0*/                                                             
0075.00   if(errcd53.BYTESAVL != 0){/* APIERR */                                       
0076.00     memcpy(msgid, errcd53.MSGID, 7);                                           
0077.00     msgid[7] = 0x00;                                                           
0078.00     memcpy(msgdta, errcd53.EXCPDATA, 100);                                     
0079.00     msgdta[100] = 0x00;                                                        
0080.00     len = triml(msgdta, ' ');                                                  
0081.00     if(len > 0) msgdta[len] = 0x00;                                            
0082.00     msgdtalen = strlen(msgdta);                                                
0083.00     msglen = sizeof(ERRMSG);                                                   
0084.00     QMHRTVM(&err_msg,  msglen, "RTVM0100", msgid, "QCPFMSG   *LIBL     ",      
0085.00        msgdta, msgdtalen, "*YES      ", "*YES      ", &errcode);               
0086.00     memcpy(msg, err_msg.msg, 512);                                             
0087.00     msg[512] = 0x00;                                                           
0088.00     len = triml(msg, ' ');                                                     
0089.00     if(len > 0) msg[len] = 0x00;                                               
0090.00     strcpy(msgtxt, msg);                                                       
0091.00     ptr = strstr(msgtxt, "&N");                                                
0092.00     if(ptr != NULL){                                                           
0093.00       pos = (int)(ptr - msgtxt);                                               
0094.00       msgtxt[pos] = 0x00;                                                      
0095.00     }                                                                          
0096.00     sprintf(ref, "LOGIN: USER=%s PASSWRD=%s", user, passwrd);       
0097.00     printf("[%d] %s\n", msgid, msgtxt);                             
0098.00     getchar();                                                      
0099.00     return;                                                         
0100.00    }/* APIERR */                                                    
0101.00                                                                     
0102.00    /*[ 指定したユーザー・プロフィールで JOB を開始する。 ]*/        
0103.00      QWTSETP(PRFHND);                                               
0104.00      printf(" ユーザー %s で仮想ユーザー環境を開始しました。 \n",   
0105.00         user);                                                      
0106.00      getchar();                                                     
0107.00      getchar();                                                     
0108.00      system("DSPJOB OPTION(*STSA)");                                
0109.00      QSYRLSPH(PRFHND);                                              
0110.00    printf(" ログオフしました。 \n");                                
0111.00    getchar();                                                       
0112.00 }                                                                   
【解説】

このプログラムは OS V3R7M0 - V5R3M0, および V5R3M0 以上での動作をカバーしている。

0049.00    _DTAA_NAME_T dtaname = {"QSS1MRI   ", "QGPL      "};
    :
0061.00   QXXRTVDA(dtaname, 1, 6, os400);

によって QGPL/QSS1MRI *DTAARA の 1-6 桁に記述されている OS リリースを読み取って、

0065.00   if(strncmp(os400, "V5R3M0", 6) >= 0){/*V5R3M0*/                         
0066.00     pos = triml(passwrd, ' ');                                            
0067.00     if(pos > 0) passwrd[pos] = 0x00;                                      
0068.00     passlen = strlen(passwrd);                                            
0069.00     passCCSID = 65535;                                                    
0070.00     QSYGETPH53(user, passwrd, PRFHND, &errcd53, passlen, passCCSID);      
0071.00   }/*V5R3M0*/                                                             
0072.00   else{/*CISC-V5R2M0*/                                                         
0073.00      QSYGETPH(user, passwrd, PRFHND, &errcd53);                                
0074.00   }/*CISC-V5R2M0*/

のようにして OS V5R3M0 以上であれば QSYGETPH53 を実行して、それ未満であれば
QSYGETPH を実行する。
QSYGETPH53 とは

0032.00 #pragma map(QSYGETPH53, "QSYS/QSYGETPH")                                     
0033.00 #pragma linkage(QSYGETPH53, OS)                                              
0034.00 void QSYGETPH53(char user[10], char* password, char PRFHND[12],              
0035.00      ERRSTRUCTURE* errcode, int passlen, int passCCSID);

で定義されているようにパスワード長等の OS V5R3M0 以降で追加されたパラメータを
含む関数として定義されている。
これによって

  • OS V5R3M0 以降とそれ未満の OS環境の両方をサポート
  • OS V5R3M0 での OS のバグの回避

の両方ともを解決している。
次に QSYGETPH で取得したハンドルを使って

0102.00    /*[ 指定したユーザー・プロフィールで JOB を開始する。 ]*/        
0103.00      QWTSETP(PRFHND);

によって仮想ユーザー環境が開始されるので

0108.00      system("DSPJOB OPTION(*STSA)");

を実行すると「現行ユーザー・プロファイル」が指定したユーザーであり
元の「ユーザー」とは異なるユーザーでログインされていることを確認することができる。
次はユーザーQTR でサインオンされている環境で、このサンプル・ブログラム TESTGETPH で
MN00 として仮想ログインしたときの DSPJOB OPTION(*STSA) が実行されいる様子である。

C/400 の実行例として仮想ログイン環境を示すために DSPJOB コマンドを実行するように
したが CLP の例ではさらに興味深い例を紹介するので、そちらも是非参照して欲しい。