#pragma module LGI$CALLOUT "LGI$CALLOUT-1-X" /* **++ ** FACILITY: LGI callouts ** ** AUTHOR: Ruslan R. Laishev ** ** MODULE DESCRIPTION: ** This module is supposed to be used to performs authentication of remote ** interactive users wich TELNETING to a local system. ** ** ** BUILD: ** This program can be built by the following procedure: ** ** $CC/NOWARN/NODEBUG LGI$CALLOUT.C ** ** Alpha/IA64: ** $LINK/SHARE/NODEBUG/NOTRACE[/SECTION_BINDING] LGI$CALLOUT.OBJ,LGI$CALLOUT.OPT/OPT ** ** A simple startup procedure: ** @LGI$CALLOUT_STARTUP.COM ** ** DESIGN ISSUE: ** This module is contains a set of LGI$ callout routines, to perform authentication ** and authorization of INTERACTIVE TELNET Sessions. ** A behavoure of the module is controled by set VMS's things: ** ** Logicals: ** /SYSTEM TELNET_ACCESS_DEBUG - If set then it will cause of producing some ** TRACE/DEBUG information to SYS$MANAGER:LGI$CALLOUT_xxXXxxXX.LOG - file. ** = TRUE ** /SYSTEM TELNET_ACCESS_SYSLOG - An IP address or IP name of SYSLOG server to send some ** information about BAD/OK logins. ** = SysLog.StarLet.SPb.RU ** ** /SYSTEM TELNET_ACCESS_NET[0-15]- A local network & mask, if IP address of remote client is in ** local network it will skip checking for access authorization by ** TELNET_ACCESS_RIGHT VMS's right id. ** = "172.16.1.0/255.255.255.0" ** = "192.168.0.0/255.255.0.0" ** ** /SYSTEM TELNET_ACCESS_BYE - A string wich will be displayed to the remote user in case ** of denied access. ** VMS Right Id: ** TELNET_ACCESS_RIGHT - If so right id is granted to the user in local SYSUAF ** it will allow to login from "external network". ** ** CAUTION: ** For correct work of the procedures under TCPWare-TCP must be set the flag: ** DEFINE/SYSTEM/NOLOG TCPWARE_TELNETD_FLAGS 256 ** ** MODIFICATION HISTORY: ** **-- */ /* ** ** INCLUDE FILES ** */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define __NEW_STARLET 1 #include #include #include "lgidef_c.h" $DESCRIPTOR(dsc_ucxdev, "UCX$DEVICE"); $DESCRIPTOR(dsc_tcpipdev, "TCPIP$DEVICE"); $DESCRIPTOR(dsc_table, "LNM$SYSTEM_TABLE"); $DESCRIPTOR(user_prmt, "!/Terminal: !AS/Remote Port Info:!AS/Host:%x!XL!/Network Username:"); $DESCRIPTOR(pass_prmt, "\r\nNetwork Password:"); $DESCRIPTOR(dsc_dbg, "TELNET_ACCESS_DEBUG"); $DESCRIPTOR(dsc_bye, "TELNET_ACCESS_BYE"); $DESCRIPTOR(dsc_id, "TELNET_ACCESS_RIGHT"); $DESCRIPTOR(dsc_nets, "TELNET_ACCESS_NET!UL"); $DESCRIPTOR(dsc_syslog, "TELNET_ACCESS_SYSLOG"); $DESCRIPTOR(dsc_vamshr, "VAMSHR"); $DESCRIPTOR(dsc_vamrtn, "VMSAUTHENTICATE"); $DESCRIPTOR(dsc_file, "SYS$MANAGER:LGI$CALLOUT_!XL.LOG"); #define INIT_DDESC(dsc) {(dsc).dsc$b_dtype = DSC$K_DTYPE_T;\ (dsc).dsc$b_class = DSC$K_CLASS_D;(dsc).dsc$w_length = 0;\ (dsc).dsc$a_pointer = 0;}; #define INIT_SDESC(dsc, len, ptr) {(dsc).dsc$b_dtype = DSC$K_DTYPE_T;\ (dsc).dsc$b_class = DSC$K_CLASS_S; (dsc).dsc$w_length = (short) (len);\ (dsc).dsc$a_pointer = (ptr);}; #define min(x,y) ((x > y)?y:x) int (*vamrtn) (...); /* ** ** CONTEXT AREA STRUCTURE ** */ #define MAX_NETS 16 struct lgictx { int initflag, /* Initialization flag */ rightid, /* The TELNET_ACCESS_RIGHT in binary form */ network[MAX_NETS],/* Network specification in the binary form */ netmask[MAX_NETS],/* Network mask specification in the binary form*/ dbg; /* Debug/Trace flag */ struct sockaddr_in slhost; /* Syslog server socket address structure */ struct dsc$descriptor bye; FABDEF fab; /* FAB & RAB of the trace file */ RABDEF rab; LGIARG_VECTOR *lgiarg; } lgictx; /* ** ** FORWARD ROUTINES DECLARATION ** */ int _init (LGIARG_VECTOR *,struct lgictx **ctx); int _identify (LGIARG_VECTOR *,struct lgictx **ctx); int _login (LGIARG_VECTOR *,struct lgictx **ctx); LGICALLOUT_VECTOR lgi$loginout_callouts = { 5, _init, /* init */ 0, /* iact_start */ 0, /* decwinit */ _identify, /* identify */ _login /* authenticate */ }; int static trace_init ( const struct lgictx *ctx ) { int status; unsigned char buf[512],buflen = 0; /* ** Clear DEBUG/TRACE flag */ ctx->dbg = 0; buflen = (unsigned char) sprintf(buf,"SYS$MANAGER:LGI$CALLOUT_%08.8X.LOG",ctx); ctx->fab = cc$rms_fab; ctx->fab.fab$b_fac = FAB$M_PUT; ctx->fab.fab$b_shr = FAB$M_SHRGET; ctx->fab.fab$l_fna = buf; ctx->fab.fab$b_fns = buflen; if ( !(1 & (status = sys$create (&ctx->fab))) ) return SS$_NORMAL; ctx->rab = cc$rms_rab; ctx->rab.rab$l_fab = &ctx->fab; if ( !(1 & (status = sys$connect (&ctx->rab))) ) return SS$_NORMAL; /* ** Initialization has been completed, set DEBUG/TRACE flag */ return (ctx->dbg = SS$_NORMAL); } void trace_out ( const struct lgictx *ctx, const char *fmts, ... ) { unsigned char buf[1024]; unsigned short buflen = 0; va_list ap; /* ** If DEBUG/TRACE flag is not set - do nothing */ if ( !ctx->dbg ) return; /* ** Formating output string */ va_start(ap,fmts); buflen = vsnprintf(buf,sizeof(buf),fmts,ap); va_end(ap); /* ** Write the record to the TRACE/DEBUG file */ ctx->rab.rab$l_rbf = buf; ctx->rab.rab$w_rsz = buflen; sys$put(&ctx->rab); } /* **++ ** FUNCTIONAL DESCRIPTION: ** ** Initializing internal contexts, translate logicals for future used. A context ** is craeted by this routine is used in sequent calls to callouts routine from this image. ** Be advised that the context is initialized an every time during new login. ** The context is created only for INTERACTIVE logins. ** ** FORMAL PARAMETERS: ** ** arg: LGIARG_VECTOR, by reference ** ctx: a pointer to context to be created ** ** RETURN VALUE: ** ** VMS Condition code ** ** SIDE EFFECTS: ** ** **-- */ static int _init ( LGIARG_VECTOR *arg, struct lgictx **ctx ) { int status,i,j; char buf [256], net[32],mask[32],lnm[128]; unsigned short buflen = 0; ILE3 itmlst[] = {{sizeof(buf),LNM$_STRING,buf,&buflen},{0,0,0,0}}; struct lgictx lctx; struct dsc$descriptor lnm_dsc; /* ** Initialization of the context */ memset(&lctx,0,sizeof(lctx)); lctx.dbg = (1 & (status = sys$trnlnm (0,&dsc_table,&dsc_dbg,0,&itmlst))); /* ** Allocate memory */ if ( !(1 & (status = lib$get_vm(&(sizeof(struct lgictx)),ctx))) ) return status; if ( lctx.dbg ) trace_init(&lctx); /* ** This loads the VAMSHR shareable image into memory if needed, then loads ** the VMSAUTHENTICATE pointer with the address of the VMSAuthenticate() ** function. Had we linked against VAMSHR, this wouldn't be necessary. */ status = lib$find_image_symbol(&dsc_vamshr,dsc_vamrtn,&vamrtn); trace_out(&lctx,"lib$find_image_symbol(%s,%s retrun status=%08X", dsc_vamshr.dsc$a_pointer,dsc_vamrtn.dsc$a_pointer,status); if ( !(1 & status) ) return status; INIT_DDESC(lctx.bye); if ( !(1 & (status = sys$trnlnm (0,&dsc_table,&dsc_bye,0,&itmlst))) || !(1 & (status = str$copy_r(&lctx.bye,&buflen,&buf))) ) lctx.bye.dsc$w_length = 0; trace_out(&lctx,"%s = %.*s",dsc_bye.dsc$a_pointer,buflen,buf); /* ** Convert VMS Right id from ASCII to Binary from */ if ( !(1 & (status = sys$asctoid(&dsc_id,&lctx.rightid,0))) ) return status; trace_out(&lctx,"%s = %08.8X",dsc_id.dsc$a_pointer,lctx.rightid); /* ** Get network/mask */ for(i=j=0;i < MAX_NETS;i++) { INIT_SDESC(lnm_dsc,sizeof(lnm),lnm); if ( !(1 & (status = sys$fao(&dsc_nets,&lnm_dsc.dsc$w_length,&lnm_dsc,i))) ) return status; trace_out(&lctx,"%.*s = ",lnm_dsc.dsc$w_length,lnm_dsc.dsc$a_pointer); if ( !(1 & (status = sys$trnlnm (0,&dsc_table,&lnm_dsc,0,&itmlst))) ) continue; trace_out(&lctx,"\t%.*s = ",buflen,buf); /* ** NET0 = "172.16.0.1/255.255.0.0" ** NET1 = "192.168.1.0/255.255.255.0" ** NET2 = "212.129.97.24/255.255.255.255" */ buf[buflen] = '\0'; if ( 2 != sscanf(buf,"%15[^/]/%15[^\n]",&net,&mask) ) continue; trace_out(&lctx,"\t\t='%s/%s'\n",net,mask); /* ** Store network/mask in binary form into the context area */ if ( -1 == inet_network(net) ) continue; lctx.network[j] = inet_network(net); lctx.netmask[j] = inet_network(mask); lctx.network[j] &= lctx.netmask[j]; trace_out(&lctx,"\t\t='%08.8X/%08.8X'",lctx.network[j],lctx.netmask[j]); j++; } /* ** */ memcpy(*ctx,&lctx,sizeof(struct lgictx)); return SS$_NORMAL; } /* **++ ** FUNCTIONAL DESCRIPTION: ** ** Form a prompt & get username. ** ** FORMAL PARAMETERS: ** ** arg: LGIARG_VECTOR, by reference ** ctx: a pointer to context has been created by _init() ** ** RETURN VALUE: ** ** VMS Condition code ** ** SIDE EFFECTS: ** ** **-- */ static int _identify( LGIARG_VECTOR *arg, struct lgictx **ctx ) { int status,ip = 0; char buf [512],host[256]; struct dsc$descriptor msg_dsc, *user = (struct dsc$descriptor *) arg->lgi$a_icr_username, *tt = (struct dsc$descriptor *) arg->lgi$a_icr_tt_phydevnam, *accport = arg->lgi$a_icr_tt_accpornam; /* ** Check process flags */ if ( !(*((short *)arg->lgi$a_icr_creprc_flags) & PRC$M_INTER) || (*((short *) arg->lgi$a_icr_creprc_flags) & PRC$M_NOPASSWORD) || (*arg->lgi$a_icr_subprocess != 0) ) return SS$_NORMAL; if ( 1 & arg->lgi$icb_autologin() ) return LGI$_SKIPRELATED; /* ** Is this is NOT a TELNET session (Terminal device: _NTAx: or _TNAx:) ** continue policy checks then. Execute OpenVMS policy function. */ if ( memcmp(tt->dsc$a_pointer,"_NTA",4) && memcmp(tt->dsc$a_pointer,"_TNA",4) ) return SS$_NORMAL; /* ** Extract a host information, translate it to the IP address ** UCX : "Remote Port Info: Host: DTV1 Port: 6480" ** TCPWare-TCP : "Remote Port Info: 172.16.1.24" ** : "Remote Port Info: 172.16.0.45,36895" */ memcpy(buf,accport->dsc$a_pointer,min(accport->dsc$w_length,sizeof(buf))); buf[min(accport->dsc$w_length,sizeof(buf))] = '\0'; trace_out(*ctx,"Remote Port Info:'%s'",buf); if ( !sscanf(buf,"Host: %[^ ]",host) && !sscanf(buf,"%[^,]",host) && !sscanf(buf,"%s",host) ) return SS$_INVLOGIN; trace_out(*ctx,"Host='%s'",host); /* ** Translate a host to the IP address, in case of any error - return a bad status */ if ( -1 == (ip = inet_network(host)) ) return SS$_NOSUCHNODE; /* ** Check the IP address against has been configured NETWORK/MASK, ** if address is match with local networks - perform a VMS Authentication */ for (int i = 0;(i < MAX_NETS) && (*ctx)->network[i];i++) { trace_out(*ctx,"%08.8X vs %08.8X/%08.8X",ip,(*ctx)->network[i],ip & (*ctx)->netmask[i]); if ( (*ctx)->network[i] == (ip & (*ctx)->netmask[i]) ) return SS$_NORMAL; } /* ** Construct a prompt to get the Username */ INIT_SDESC(msg_dsc,sizeof(buf),buf); if ( !(1 & (status = sys$fao (&user_prmt,&msg_dsc.dsc$w_length,&msg_dsc, tt,accport,ip))) ) return status; if ( !(1 & (status = arg->lgi$icb_userprompt(&msg_dsc))) ) return status; return LGI$_SKIPRELATED; } /* This is the callback that's called when the ACE server tells us that */ /* interaction with the user is necessary (a prompt/response thing). */ int IOCallback ( char *Prompt, char *Response, int minl, int maxl, int RespType, int EchoFlag, int Timeout, struct lgictx *ctx ) { RABDEF *ttrab = (RABDEF *) ctx->lgiarg->lgi$a_icr_input_rab; int status,rne = ttrab->rab$v_rne,vtmo = ttrab->rab$v_tmo,tmo = ttrab->rab$b_tmo; if ( Prompt && *Prompt ) { ttrab->rab$v_pmt = 1; ttrab->rab$l_pbf = Prompt; ttrab->rab$b_psz = strnlen(Prompt,512); } /* ** Now read from the terminal - the flag tells us if the info ** read in should be echoed to the terminal. ** ** Set needed RAB's fields & perform reading from the Terminal */ if ( Timeout ) ttrab->rab$v_tmo = ttrab->rab$b_tmo = Timeout; ttrab->rab$l_ubf = Response; ttrab->rab$w_usz = maxl; ttrab->rab$v_rne = !EchoFlag; status = sys$get(ttrab); /* ** Restore has been changed RAB's fields if ( Timeout ) { ttrab->rab$v_tmo= vtmo; ttrab->rab$b_tmo= tmo; } ttrab->rab$v_rne = rne; trace_out("status=%08X,stv=%08X,rsz=%u",status,ttrab->rab$l_stv,ttrab->rab$w_rsz); /* ** Postprocess a reading ** ** Return error status in case of any $GET's error */ if ( !(1 & status) || !(1 & ttrab->rab$l_stv) ) return 0; /* ** Terminate the gotten string witk ZERO, and return good status */ Response[ttrab->rab$w_rsz] = '\0'; return SS$_NORMAL; } /* This callback is used when the ACE server tells us there's a message */ /* to be printed out to the user. */ int InfoCallback ( char *Prompt, int Timeout, struct lgictx *ctx ) { RABDEF *ttrab = (RABDEF *) ctx->lgiarg->lgi$a_icr_input_rab; ttrab->rab$l_rbf = Prompt; ttrab->rab$w_rsz = strnlen(Prompt,512); sys$put(ttrab); return SS$_NORMAL; } /* If we're supposed to clear the screen, this callback is used. We don't */ /* do anything with it for this test program. */ void ScreenClearCallback(void) {} /* The callback when a timer is exceeded. We don't do much with it. */ void TimeoutCallback (void) {} /* **++ ** FUNCTIONAL DESCRIPTION: ** ** This routine perform an authentication of the user. ** ** FORMAL PARAMETERS: ** ** arg: LGIARG_VECTOR, by reference ** ctx: a pointer to context has been created by _init() ** ** RETURN VALUE: ** ** VMS Condition code ** ** SIDE EFFECTS: ** ** **-- */ static int _login ( LGIARG_VECTOR *arg, struct lgictx **ctx ) { int status,context = 0,uai_id = 0; char buf [256],suser[128]; struct dsc$descriptor *user = (struct dsc$descriptor *) arg->lgi$a_icr_username; struct { union uicdef uai_uic; int mbz; } holder = {0,0}; ILE3 get_itmlst[] = {{4,UAI$_UIC,&holder.uai_uic,0},{0,0,0,0}}; /* ** Check process's flag */ if ( !(*((short *)arg->lgi$a_icr_creprc_flags) & PRC$M_INTER) || (*((short *)arg->lgi$a_icr_creprc_flags) & PRC$M_NOPASSWORD) || (*arg->lgi$a_icr_subprocess != 0) ) return SS$_NORMAL; /* ** Alway validate a SYSTEM user against a local SYSUAF!!! */ if ( user && (6 == user->dsc$w_length) && !memcmp("SYSTEM",user->dsc$a_pointer,user->dsc$w_length) ) return SS$_NORMAL; /* ** Is the user's access has been authorized by the TELNET_ACCESS_RIGHT id? */ if ( (*ctx)->rightid ) { /* ** Retrive USER's UIC from the SYSUAF */ if ( !(1 & (status = sys$getuai(EFN$C_ENF,0,user,&get_itmlst,0,0,0))) ) return status; /* ** Look for the TELNET_ACCESS_RIGHT id ... */ while ( 1 & (status = sys$find_held(&holder,&uai_id,0,&context)) ) if ( (*ctx)->rightid == uai_id ) break; sys$finish_rdb (&context); trace_out(*ctx,"uai_id = %08.8X vs right id = %08.8X,status = %08.8X",uai_id,(*ctx)->rightid,status); if ( !(1 & status) ) return status; } /* ** Calling VAM service to perform authentication of the user with SecureID, RADIUS... */ (*ctx)->lgiarg = arg; memcpy(suser,user->dsc$a_pointer,user->dsc$w_length); suser[min(sizeof(suser),user->dsc$w_length)] = '\0'; status = vamrtn("SECURID",suser,0, IOCallback,InfoCallback,ScreenClearCallback,TimeoutCallback, *ctx,0,0,0); /* ** */ return (1 & status)?LGI$_SKIPRELATED:status; }