#pragma module MX_2SMS_NOTIFY "MX_2SMS_NOTIFY-1-D" #define __MODULE__ "MX_2SMS_NOTIFY" /* **++ ** FACILITY: MX 2 SMS Notify ** ** ABSTRACT: Performs a notification by SMS about incoming mail. ** This module based on the FILTER.C by Matt Madison. ** ** AUTHOR: Ruslan R. Laishev ** ** CREATION DATE: 30-DEC-2002 ** ** MODULE DESCRIPTION: ** ** This module contains routines for use by the MX Router to sending ** notification "You have got a mail" with Short Message Service. ** A logic of processing: ** - extract an username from the email address ** - check that the SKL$EMAIL2SMS right id has been granted to the user ** - extract phone number from the SYSUAF Owner field ** - form and send SMS ** ** ** BUILD: ** ** $ CC MX_2SMS_NOTIFY.C ** $ LINK/NOTRACE/SHARE/EXEC=MX_EXE:FILTERSHR.EXE FILTER.OBJ,SYS$INPUT:/OPT ** ** For VAX systems, enter the following linker options: ** SYS$LIBRARY:VAXCRTL/SHARE (only if using VAX C, not DEC C) ** UNIVERSAL=INIT,FILTER,FINISH ** For Alpha systems, enter these options instead: ** SYMBOL_VECTOR=(INIT=PROCEDURE,FILTER=PROCEDURE,FINISH=PROCEDURE) ** ** Once linked, define the following logical name: ** ** $ DEFINE/SYSTEM/EXEC MX_SITE_MESSAGE_FILTER MX_EXE:MX_2SMS_NOTIFY.EXE ** ** $ DEFINE/SYSTEM/EXEC MX_2SMS_CONNSTS "SMSC:9000/STARLET/OpenVMS/smtp/2/1" ** $ DEFINE/SYSTEM/EXEC MX_2SMS_FROM 88127163222 ** ** Then stop and restart or reset the MX Router: ** ** $ MCP RESET ROUTER ** ** ** MODIFICATION HISTORY: ** 18-JUL-2003 RRL Added a checking of right id (SKL$EMAIL2SMS) before sending ** SMS notification. ** 1-AUG-2003 RRL Added de-MIME-ing of the subject field; now SMS body contains a ** size of the mail,disquota and free size. ** 28-NOV-2003 RRL Get message size from ENV items. ** 27-MAY-2005 RRL Use "sender" from header instead of envelope. ** 3-JUN-2005 RRL Extract a mail adress from the "sender" string. ** 6-SEP-2005 RRL Now Subject: [SPAM] -> X-PMAS-Spam: Yes ** 21-MAY-2009 RRL Added two configuration logicals (/sys/exec) : MX_2SMS_CONNSTS, ** MX_2SMS_FROM; ** 9-JUN-2009 RRL Changed logging. ** 10-SEP-2009 RRL Modify to keep SMPP/ESME API context between calls. ** 29-JUN-2010 RRL Skip 'Notifications' from mail.ru. ** **-- */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mx_hdr.h" #include "mx_filterdef.h" #include "smppdef.h" #include "esme_msg.h" #include "mx_2sms_notify_msg.h" #define min(x,y) ((x > y)?y:x) #define max(x,y) ((x < y)?y:x) #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);} int debug_flag = 0, zero = 0; #define DEBUG if(debug_flag)printf("%-24.24s:%-08.0u ",__MODULE__,__LINE__);if(debug_flag)printf char stype[32],ton,npi; short stypelen; /* ** SMPP/ESME API context */ void *ectx = NULL; /* ** RUSSIAN CHARACTER SET TABLES */ $DESCRIPTOR(iso_dsc,"ISO-8859-5"); $DESCRIPTOR(koi_dsc,"KOI8-R"); $DESCRIPTOR(win_dsc,"WINDOWS-1251"); #define CS$UNDEF 0 #define CS$ISOCYR 0x01 #define CS$KOI8R 0x02 #define CS$CP1251 0x03 $DESCRIPTOR(tabnam, "LNM$SYSTEM_TABLE"); $DESCRIPTOR(ln1, "MX_2SMS_CONNSTS"); $DESCRIPTOR(ln2, "MX_2SMS_FROM"); const char accmode = PSL$C_EXEC; char connsts[255]; /* = "SMSC:9000/STARLET/65sgfjh/smtp/2/1"; */ char fromsrc [255]; /* = "88127150505"; */ short fromsrclen; $DESCRIPTOR(digits_dsc, "0123456789"); $DESCRIPTOR(XSPAM_dsc, "X-PMAS-Spam: Yes"); char notify [] = "\"Mail.Ru Notification Service\""; $DESCRIPTOR(id_sendsms_dsc, "SKL$EMAIL2SMS"); char sms_fmt [] = "Внимание!\n\ Письмо (%uKБ)\n\ не может быть\n\ помещено в Ваш\n\ почтовый ящик.\n\ Свободно %uKB\n\ из %uKB."; /* **++ ** FUNCTIONAL DESCRIPTION: ** ** Put formated message to standard output device (SYS$OUTPUT). ** ** FORMAL PARAMETERS: ** ** ctx: A session context ** msgid: VMS condition code ** variable agriments list ** ** RETURN VALUE: ** ** VMS condition code ** ** **-- */ int _log ( int msgid, ... ) { long status,retvalue = msgid; va_list ap; char buf[4096] = {"!%D "},outadr[4],msg_buf[4096]; struct dsc$descriptor opr_dsc,buf_dsc,fao_dsc; int argc,argl[32],idx,flag=15,lvl; /* ** Get a message text with given msgid */ INIT_SDESC(fao_dsc,sizeof(buf)-4,&buf[4]); if ( !(1 & (status = sys$getmsg (msgid,&fao_dsc.dsc$w_length,&fao_dsc,flag,&outadr))) ) lib$signal(status); /* ** Reorganize parameters list for $FAOL */ va_start(ap,msgid); argl[0] = 0; for (idx = 1,va_count(argc);idx < argc;idx++) argl[idx] = va_arg(ap,unsigned); va_end((void *) msgid); /* ** Format a message, put it to SYS$OUTPUT */ fao_dsc.dsc$a_pointer -=4; fao_dsc.dsc$w_length +=4; INIT_SDESC(buf_dsc, sizeof(msg_buf),msg_buf); if ( !(1 & (status = sys$faol(&fao_dsc,&buf_dsc.dsc$w_length,&buf_dsc,argl))) ) lib$signal(status); lib$put_output(&buf_dsc); return retvalue; } /* **++ ** FUNCTIONAL DESCRIPTION: ** ** Returns a diskquota information for user specified by UIC on ** the specified device. ** ** FORMAL PARAMETERS: ** ** uic: A pointer to the UIC info ** dev: A device name ** total: a returned disk quota in bytes ** free: a returned not used free space ** ** RETURN VALUE: ** ** VMS condition code ** **-- */ int dquota ( unsigned uic, struct dsc$descriptor *dev, unsigned *total, unsigned *free ) { int status,chan = 0; iosb io_status; struct _dqf quota; struct fibdef1 quota_fib; struct simple__desc { int cnt; void *ptr; } qfun = {sizeof(struct fibdef1),"a_fib}, qpar = {sizeof(struct _dqf),"a}; quota.dqf$l_uic = uic; quota_fib.fib$w_cntrlfunc= FIB$C_EXA_QUOTA; quota_fib.fib$l_cntrlval = 0; *total = *free = quota.dqf$l_permquota = quota.dqf$l_usage = 0; if ( (1 & (status = sys$assign(dev,&chan,0,0,0))) ) { status = sys$qiow(EFN$C_ENF,chan,IO$_ACPCONTROL,&io_status,0,0,&qfun,&qpar,0,&qpar,0,0); sys$dassgn(chan); *total = (512*quota.dqf$l_permquota)/1024; *free = (512*(quota.dqf$l_permquota-quota.dqf$l_usage))/1024; } return (!(1 & status)?status:io_status.iosb$w_status); } int ia__chkid ( struct dsc$descriptor_s *dsc$right, unsigned uic ) { unsigned status,id,uai_id,context = 0; struct { union uicdef uai_uic; int mbz; } holder = {uic,0}; /* ** Convert VMS Right id from ASCII to Binary from */ if ( !(1 & (status = sys$asctoid(dsc$right,&id,0))) ) return status; /* ** Find for presence of right_id for UIC */ while ( 1 & (status = sys$find_held(&holder,&uai_id,0,&context)) ) if ( id == uai_id ) break; sys$finish_rdb (&context); return status; } int esmeio_shut ( void *ectx ) { int status; if ( !ectx ) return SS$_NORMAL; /* ** Send a UNBIND request to close session with the SMSC */ status = esme_api_tx(ectx,SMPP_CMD$K_UNBIND,NULL,NULL,NULL); /* ** Shutdown ESME API */ return esme_api_shut(&ectx); } int esmeio_init ( void **ectx, char *connsts ) { int status,resp, sts,seq; char buf[256],name[256],sid[32],pwd[32],ston[4],snpi[4]; struct dsc$descriptor buf_dsc,name_dsc; /* ** Got a connection string, we expect to see: ** "SMSC.DeltaTel.RU:9000/symb/VAX/zztop/1/1" */ if ( 6 != sscanf(connsts,"%[^/]/%[^/]/%[^/]/%[^/]/%[^/]/%[^\n]",&name,&sid,&pwd,&stype,&ston,&snpi) ) return SS$_INSFARG; /* ** Initalize ESME API */ INIT_SDESC(name_dsc,strnlen(name,sizeof(name)),name); if ( !(1 & (status = esme_api_init(ectx,&name_dsc))) ) return status; stypelen = (char) strnlen(stype,sizeof(stype)); ton = (char)atoi(ston); npi = (char)atoi(snpi); /* ** Send a BIND request to open a session with the SMSC */ { /* ** Items list for BIND_TRANSMITTER request */ ile3 bind_items [] = { {strnlen(sid,sizeof(sid)), SMPP_PTAG$K_SID, &sid}, {strnlen(pwd,sizeof(pwd)), SMPP_PTAG$K_PWD, &pwd}, {stypelen, SMPP_PTAG$K_STYPE, &stype}, {1, SMPP_PTAG$K_TON, &ton}, {1, SMPP_PTAG$K_NPI, &npi}, {0,0,0}}; if ( !(1 & (status = esme_api_tx(*ectx,SMPP_CMD$K_BIND_TRANSMITTER,bind_items,NULL,NULL))) ) return status; } /* ** Receive a RESPONSE to the sent BIND request */ status = esme_api_rx(*ectx,NULL,&resp,&sts,NULL,NULL,&seq); /* ** Return a translated to VMS condition code SMPP/ESME error status */ return (status & 1)?esme_api_err2cond(sts):status; } int esmeio_sendsms ( void *ectx, char *src, short srclen, char *dest, short destlen, char *sms, short smslen ) { int status,resp,sts,seq; /*YYMMDDhhmmsstnnp */ char dc = DC$K_ISOCYR,expire [] = "000000020000000R"; struct dsc$descriptor buf_dsc,name_dsc; ile3 submit_items [] = { {stypelen, SMPP_PTAG$K_STYPE, &stype}, {1, SMPP_PTAG$K_SRCTON, &ton}, {1, SMPP_PTAG$K_SRCNPI, &npi}, {srclen, SMPP_PTAG$K_SRC, src}, {1, SMPP_PTAG$K_DESTTON, &ton}, {1, SMPP_PTAG$K_DESTNPI, &npi}, {destlen, SMPP_PTAG$K_DEST, dest}, {1, SMPP_PTAG$K_DC, &dc}, {1, SMPP_PTAG$K_SMSLEN, &zero}, {sizeof(expire)-1, SMPP_PTAG$K_PERIOD, expire}, // {smslen, SMPP_PTAG$K_SMSBODY, sms}, {smslen, SMPP_PTAG$K_MSGPAYLD, sms}, {0,0,0}}; /* ** Send a SMS by SUBMIT_SM request */ if ( !(1 & (status = esme_api_tx(ectx,SMPP_CMD$K_SUBMIT_SM,submit_items,NULL,NULL))) ) return status; /* ** Receive a RESPONSE to the sent SUBMIT_SM request */ status = esme_api_rx(ectx,NULL,&resp,&sts,NULL,NULL,&seq); /* ** Return a translated to VMS condition code SMPP/ESME error status */ return (status & 1)?esme_api_err2cond(sts):status; } /* **++ ** ROUTINE: INIT ** ** FUNCTIONAL DESCRIPTION: ** ** Initializes context for the message filter. ** ** RETURNS: condition value ** ** **-- */ int INIT (void **contxt) { int status; short retlen = 0; ile3 itmlst[] = {{0,LNM$_STRING,0,&retlen},{0,0,0,0}}; /* ** Get logical name with connection string */ itmlst[0].ile3$ps_bufaddr = &connsts; itmlst[0].ile3$w_length = sizeof(connsts); if ( !(1 & (status = sys$trnlnm (0,&tabnam,&ln1,&accmode,&itmlst))) ) return _log(status); connsts[retlen] = '\0'; itmlst[0].ile3$ps_bufaddr = &fromsrc; itmlst[0].ile3$w_length = sizeof(fromsrc); if ( !(1 & (status = sys$trnlnm (0,&tabnam,&ln2,&accmode,&itmlst))) ) return _log(status); fromsrc[fromsrclen = retlen] = '\0'; return SS$_NORMAL; } /* **++ ** ROUTINE: FINISH ** ** FUNCTIONAL DESCRIPTION: ** ** Cleans up after a filter run. ** ** RETURNS: condition value ** **-- */ int FINISH (void **contxt) { return SS$_NORMAL; } int send_sms ( char *to, short tolen, char *sms, short smslen ) { int status,buflen = 0; DEBUG("SMS (%u bytes) from '%.*s' to '%.*s'\n%.*s\n", smslen,fromsrclen,fromsrc,tolen,to,smslen,sms); /* ** Initialize SMPP/ESME API context if need */ if ( !ectx && !(1 & (status = esmeio_init(&ectx,connsts))) ) { esmeio_shut(ectx); return status; } status = esmeio_sendsms(ectx,fromsrc,fromsrclen,to,tolen,sms,smslen); DEBUG("SMS (%u bytes) from '%.*s' to '%.*s',status = %x\n", smslen,fromsrclen,fromsrc,tolen,to,status); /* ** In case of any error shut SMPP/EXME context */ if ( !(1 & status) ) { esmeio_shut(ectx); ectx = NULL; } return status; } void sms_notify ( ile3 *from, ile3 *to, unsigned dc, ile3 *subj, unsigned mailsz ) { unsigned status, uai_uic,total = 0,free = 0,flag = 0; char buf[SMPP$K_SMSLEN + 512],owner [64] = {0},uai_defdev[32]; void *cp,*cp2; short buflen = 0,chan; ile3 get_items [] = { {32, UAI$_OWNER, &owner, 0}, {4, UAI$_UIC, &uai_uic, 0}, {32, UAI$_DEFDEV, &uai_defdev, 0}, {0, 0, 0, 0}}; struct dsc$descriptor tmp_dsc; char dom[128]; short domlen; char *domlist [] = { "mail.skylink.spb.ru", "skylink.spb.ru", "spb.skylink.ru", "starlet.deltatelecom.ru", "starlet.deltatel.ru", "starlet.skylink.spb.ru"}; /* ** Extract from e-mail */ if ( cp = memchr(from->ile3$ps_bufaddr,'<',from->ile3$w_length-1) ) { cp++; from->ile3$w_length = cp - from->ile3$ps_bufaddr; from->ile3$ps_bufaddr = cp; if ( cp = memchr(cp,'>',from->ile3$w_length) ) from->ile3$w_length = cp - from->ile3$ps_bufaddr; } /* ** Extract a local user name from the address string */ if ( *((char*) to->ile3$ps_bufaddr) == '<' ) { if ( !(cp = memchr(to->ile3$ps_bufaddr+1,'@',to->ile3$w_length-1)) ) return; INIT_SDESC(tmp_dsc,(cp-to->ile3$ps_bufaddr)-1,to->ile3$ps_bufaddr+1); } else { if ( !(cp = memchr(to->ile3$ps_bufaddr,'@',to->ile3$w_length)) ) return; INIT_SDESC(tmp_dsc,(cp-to->ile3$ps_bufaddr)-1,to->ile3$ps_bufaddr); } /* ** Check domain name against the domain list */ cp2 = strchr(cp+1,'>'); domlen = cp2 - (cp+1); memcpy(dom,cp+1,domlen); DEBUG("usr ->'%.*s'\n",tmp_dsc.dsc$w_length,tmp_dsc.dsc$a_pointer); DEBUG("dom ->'%.*s',domlen = %u\n",domlen,dom,domlen); if ( strncasecmp(dom,domlist[0],domlen) && strncasecmp(dom,domlist[1],domlen) && strncasecmp(dom,domlist[2],domlen) && strncasecmp(dom,domlist[3],domlen) && strncasecmp(dom,domlist[4],domlen) ) return; /* ** Get information from the SYSUAF */ str$upcase(&tmp_dsc,&tmp_dsc); if ( !(1 & (status = sys$getuai(EFN$C_ENF,NULL, &tmp_dsc, &get_items, 0, 0, 0))) ) return; /* ** We expect to see ISDN in the owner field */ INIT_SDESC(tmp_dsc,owner[0],&owner[1]); if ( (owner[0] != 10) || str$find_first_not_in_set(&tmp_dsc,&digits_dsc) ) return; DEBUG("isdn->'%.*s'\n",tmp_dsc.dsc$w_length,tmp_dsc.dsc$a_pointer); if ( !(1 & (status = ia__chkid(&id_sendsms_dsc,uai_uic))) ) return; memcpy(buf,"Fr:",3); buflen = 3; memcpy(buf+buflen,from->ile3$ps_bufaddr,min(13,from->ile3$w_length)); buflen += min(13,from->ile3$w_length); DEBUG("mailsz->%u\n",mailsz); DEBUG("dev ->'%.*s'\n",uai_defdev[0],&uai_defdev[1]); INIT_SDESC(tmp_dsc,uai_defdev[0],&uai_defdev[1]); if ( (1 & (status = dquota(uai_uic,&tmp_dsc,&total,&free))) && (mailsz > free) ) { buflen += sprintf(buf+buflen,sms_fmt,mailsz?mailsz:1,free,total); DEBUG("dquota = %d\n",status); } else if ( subj ) { if ( buflen % 16 ) buf[buflen++] = '\n'; memcpy(buf+buflen,"Sj:",3); buflen += 3; memcpy(buf+buflen,subj->ile3$ps_bufaddr,min(96,subj->ile3$w_length)); buflen += min(96,subj->ile3$w_length); DEBUG("dc ->'%u'\n",dc); if ( dc ) convdc(dc,buf,&buflen); } DEBUG("sms ->'%.*s'\n",buflen,buf); owner[0] = '8'; if ( !(1 & (status = send_sms(owner,11,buf,min(SMPP$K_SMSLEN,buflen)))) ) { _log(status); _log(MX2SMS_ERRSEND,from->ile3$w_length,from->ile3$ps_bufaddr, to->ile3$w_length,to->ile3$ps_bufaddr); } else _log(MX2SMS_SENT, min(64,from->ile3$w_length),from->ile3$ps_bufaddr, to->ile3$w_length,to->ile3$ps_bufaddr,11,owner); DEBUG("status = %d\n",status); } /* **++ ** ROUTINE: FILTER ** ** FUNCTIONAL DESCRIPTION: ** ** Runs the message filter. ** ** RETURNS: condition value ** ** PROTOTYPE: ** ** FILTER contxt, envlst, hdrlst, msgfile, addrcpt, addhdrs ** ** contxt: user_arg, longword (unsigned), modify, by reference ** envlst: item_list_3, longword (unsigned), read only, by reference ** hdrlst: item_list_3, longword (unsigned), read only, by reference ** msgfile: char_string, character string, read only, by descriptor ** addrcpt: address (of item_list_3), longword (unsigned), write only, by reference ** addhdrs: address (of item_list_3), longword (unsigned), write only, by reference ** **-- */ int FILTER ( void **contxt, ile3 *envlst, ile3 *hdrlst, struct dsc$descriptor *msgfile, void **addrcpts, void **addhdrs ) { unsigned dc = 0,sz = 0; ile3 *itm,*to = NULL,*from = NULL,*cs = NULL, *subj = NULL; char dst [512], *cp; short retlen; DEBUG("----------------------------------------------------------------\n"); /* ** Run through the envelope itemlist. */ for (itm = envlst; itm->ile3$w_code && itm->ile3$w_length ; itm++) { switch (itm->ile3$w_code) { case FLTR__ENV_SENDER: from = itm; // DEBUG("code = %u,'%.*s'\n",itm->ile3$w_code,itm->ile3$w_length,itm->ile3$ps_bufaddr); break; case FLTR__ENV_RECIPIENT: to = itm; // DEBUG("code = %u,'%.*s'\n",itm->ile3$w_code,itm->ile3$w_length,itm->ile3$ps_bufaddr); break; case FLTR__ENV_MSGSIZE: sz = *((unsigned *)itm->ile3$ps_bufaddr); break; } } /* ** Run through the header itemlist. */ for (itm = hdrlst; itm->ile3$w_code && itm->ile3$w_length; itm++) { /* ** Check for 'X-PMAS-Spam: Yes' in the mail header ** if present stop processing. */ if ( (XSPAM_dsc.dsc$w_length == itm->ile3$w_length) && !memcmp(itm->ile3$ps_bufaddr,XSPAM_dsc.dsc$a_pointer,XSPAM_dsc.dsc$w_length) ) { _log(MX2SMS_IGNORSPAM,from->ile3$w_length,from->ile3$ps_bufaddr, to->ile3$w_length,to->ile3$ps_bufaddr); return SS$_NORMAL; } switch (itm->ile3$w_code) { case MX_K_HDR_MIME_C_TYPE: cs = itm; // DEBUG("code = %u,'%.*s'\n",itm->ile3$w_code,itm->ile3$w_length,itm->ile3$ps_bufaddr); break; case MX_K_HDR_SUBJECT: subj = itm; // DEBUG("code = %u,'%.*s'\n",itm->ile3$w_code,itm->ile3$w_length,itm->ile3$ps_bufaddr); break; case MX_K_HDR_FROM: from = itm; // DEBUG("code = %u,'%.*s'\n",itm->ile3$w_code,itm->ile3$w_length,itm->ile3$ps_bufaddr); break; } } if ( !from || !to ) return SS$_NORMAL; DEBUG("from->'%.*s'\n",from->ile3$w_length,from->ile3$ps_bufaddr); DEBUG("to ->'%.*s'\n",to->ile3$w_length,to->ile3$ps_bufaddr); DEBUG("cs ->'%.*s'\n",cs?cs->ile3$w_length:0,cs?cs->ile3$ps_bufaddr:NULL); DEBUG("subj->'%.*s'\n",subj?subj->ile3$w_length:0,subj?subj->ile3$ps_bufaddr:NULL); DEBUG("size->%u bytes\n",sz); sz /= 1024; DEBUG("size->%u KBytes\n",sz); /* ** Don't process mails from "<>" */ if ( from->ile3$w_length < 2 ) { DEBUG("Field From is too short - ignore mail\n"); return SS$_NORMAL; } if ( !strncasecmp(from->ile3$ps_bufaddr,notify,min(sizeof(notify)-1,from->ile3$w_length)) ) { DEBUG("Ignore from 'autoreply'\n"); return SS$_NORMAL; } if ( (cp = memchr(from->ile3$ps_bufaddr,'<',from->ile3$w_length)) ) { from->ile3$ps_bufaddr = cp; from->ile3$w_length = cp - (char *) (++from->ile3$ps_bufaddr); if ( (cp = memchr(from->ile3$ps_bufaddr,'>',to->ile3$w_length)) ) from->ile3$w_length = cp - (char *) (from->ile3$ps_bufaddr); } { if ( subj && subj->ile3$ps_bufaddr && subj->ile3$w_length ) { int status; if ( !(1 & (status = demime(subj->ile3$ps_bufaddr,subj->ile3$w_length,dst,sizeof(dst), &retlen,&dc))) ) { _log(status); _log(MX2SMS_ERRSUBJ,from->ile3$w_length,from->ile3$ps_bufaddr, to->ile3$w_length,to->ile3$ps_bufaddr); } subj->ile3$ps_bufaddr = dst; subj->ile3$w_length = retlen; } } DEBUG("subj->'%.*s'\n",subj?subj->ile3$w_length:0,subj?subj->ile3$ps_bufaddr:NULL); DEBUG("dc ->'%u'\n",dc); if ( !dc && cs && cs->ile3$w_length ) { struct dsc$descriptor tmp_dsc; /* ** Match a character set of the mail */ INIT_SDESC(tmp_dsc,cs->ile3$w_length,cs->ile3$ps_bufaddr); str$upcase(&tmp_dsc,&tmp_dsc); if ( str$position(&tmp_dsc,&win_dsc,NULL) ) dc = CS$CP1251; else if ( str$position(&tmp_dsc,&iso_dsc,NULL) ) dc = CS$ISOCYR; else dc = CS$KOI8R; } DEBUG("dc ->'%u'\n",dc); sms_notify(from,to,dc,subj,sz); return SS$_NORMAL; }