/* $Id: mail.c,v 1.28 2003/07/20 00:55:34 in2 Exp $ */
#include "bbs.h"
char currmaildir[32];
static char msg_cc[] = "\033[32m[群組名單]\033[m\n";
static char listfile[] = "list.0";
static int mailkeep = 0, mailsum = 0;
static int mailsumlimit = 0, mailmaxkeep = 0;
int
setforward()
{
char buf[80], ip[50] = "", yn[4];
FILE *fp;
sethomepath(buf, cuser.userid);
strcat(buf, "/.forward");
if ((fp = fopen(buf, "r"))) {
fscanf(fp, "%s", ip);
fclose(fp);
}
getdata_buf(b_lines - 1, 0, "請輸入信箱自動轉寄的email地址:",
ip, sizeof(ip), DOECHO);
if (ip[0] && ip[0] != ' ') {
getdata(b_lines, 0, "確定開啟自動轉信功\能?(Y/n)", yn, sizeof(yn),
LCECHO);
if (yn[0] != 'n' && (fp = fopen(buf, "w"))) {
move(b_lines, 0);
clrtoeol();
fprintf(fp, "%s", ip);
fclose(fp);
outs("設定完成!");
refresh();
return 0;
}
}
move(b_lines, 0);
clrtoeol();
outs("取消自動轉信!");
unlink(buf);
refresh();
return 0;
}
int
built_mail_index()
{
char genbuf[128];
move(b_lines - 4, 0);
outs("本功\能只在信箱檔毀損時使用,\033[1;33m無法\033[m救回被刪除的信件。\n"
"除非您清楚這個功\能的作用,否則\033[1;33m請不要使用\033[m。\n"
"警告:任意的使用將導致\033[1;33m不可預期的結果\033[m!\n");
getdata(b_lines - 1, 0,
"確定重建信箱?(y/N)", genbuf, 3,
LCECHO);
if (genbuf[0] != 'y')
return 0;
snprintf(genbuf, sizeof(genbuf),
BBSHOME "/bin/buildir " BBSHOME "/home/%c/%s",
cuser.userid[0], cuser.userid);
move(22, 0);
prints("\033[1;31m已經處理完畢!! 諸多不便 敬請原諒~\033[m");
pressanykey();
system(genbuf);
return 0;
}
int
mailalert(char *userid)
{
userinfo_t *uentp = NULL;
int n, tuid, i;
if ((tuid = searchuser(userid)) == 0)
return -1;
n = count_logins(tuid, 0);
for (i = 1; i <= n; i++)
if ((uentp = (userinfo_t *) search_ulistn(tuid, i)))
uentp->mailalert = 1;
return 0;
}
int
mail_muser(userec_t muser, char *title, char *filename)
{
return mail_id(muser.userid, title, filename, cuser.userid);
}
/* Heat: 用id來寄信,內容則link準備好的檔案 */
int
mail_id(char *id, char *title, char *filename, char *owner)
{
fileheader_t mhdr;
char genbuf[128];
sethomepath(genbuf, id);
if (stampfile(genbuf, &mhdr))
return 0;
strlcpy(mhdr.owner, owner, sizeof(mhdr.owner));
strncpy(mhdr.title, title, TTLEN);
mhdr.filemode = 0;
Link(filename, genbuf);
sethomedir(genbuf, id);
append_record(genbuf, &mhdr, sizeof(mhdr));
mailalert(id);
return 0;
}
int
invalidaddr(char *addr)
{
if (*addr == '\0')
return 1; /* blank */
while (*addr) {
if (not_alnum(*addr) && !strchr("[].@-_", *addr))
return 1;
addr++;
}
return 0;
}
int
m_internet()
{
char receiver[60];
getdata(20, 0, "收信人:", receiver, sizeof(receiver), DOECHO);
if (strchr(receiver, '@') && !invalidaddr(receiver) &&
getdata(21, 0, "主 題:", save_title, STRLEN, DOECHO))
do_send(receiver, save_title);
else {
move(22, 0);
outs("收信人或主題不正確, 請重新選取指令");
pressanykey();
}
return 0;
}
void
m_init()
{
sethomedir(currmaildir, cuser.userid);
}
int
chkmailbox()
{
if (!HAVE_PERM(PERM_SYSOP) && !HAVE_PERM(PERM_MAILLIMIT)) {
int max_keepmail = MAX_KEEPMAIL;
if (HAS_PERM(PERM_SYSSUBOP) || HAS_PERM(PERM_SMG) ||
HAS_PERM(PERM_PRG) || HAS_PERM(PERM_ACTION) || HAS_PERM(PERM_PAINT)) {
mailsumlimit = 700;
max_keepmail = 500;
} else if (HAS_PERM(PERM_BM)) {
mailsumlimit = 500;
max_keepmail = 300;
} else if (HAS_PERM(PERM_LOGINOK))
mailsumlimit = 200;
else
mailsumlimit = 50;
mailsumlimit += cuser.exmailbox * 10;
mailmaxkeep = max_keepmail + cuser.exmailbox;
m_init();
if ((mailkeep = get_num_records(currmaildir, sizeof(fileheader_t))) >
mailmaxkeep) {
move(b_lines, 0);
clrtoeol();
bell();
prints("您保存信件數目 %d 超出上限 %d, 請整理",
mailkeep, mailmaxkeep);
bell();
refresh();
igetch();
return mailkeep;
}
if ((mailsum = get_sum_records(currmaildir, sizeof(fileheader_t))) >
mailsumlimit) {
move(b_lines, 0);
clrtoeol();
bell();
prints("您保存信件容量 %d(k)超出上限 %d(k), 請整理",
mailsum, mailsumlimit);
bell();
refresh();
igetch();
return mailkeep;
}
}
return 0;
}
static void
do_hold_mail(char *fpath, char *receiver, char *holder)
{
char buf[80], title[128];
fileheader_t mymail;
sethomepath(buf, holder);
stampfile(buf, &mymail);
mymail.filemode = FILE_READ | FILE_HOLD;
strlcpy(mymail.owner, "[備.忘.錄]", sizeof(mymail.owner));
if (receiver) {
snprintf(title, sizeof(title), "(%s) %s", receiver, save_title);
strncpy(mymail.title, title, TTLEN);
} else
strlcpy(mymail.title, save_title, sizeof(mymail.title));
sethomedir(title, holder);
unlink(buf);
Link(fpath, buf);
/* Ptt: append_record->do_append */
do_append(title, &mymail, sizeof(mymail));
}
void
hold_mail(char *fpath, char *receiver)
{
char buf[4];
getdata(b_lines - 1, 0, "已順利寄出,是否自存底稿(Y/N)?[N] ",
buf, sizeof(buf), LCECHO);
if (buf[0] == 'y')
do_hold_mail(fpath, receiver, cuser.userid);
}
int
do_send(char *userid, char *title)
{
fileheader_t mhdr;
char fpath[STRLEN];
char receiver[IDLEN];
char genbuf[200];
int internet_mail, i;
if (strchr(userid, '@'))
internet_mail = 1;
else {
internet_mail = 0;
if (!getuser(userid))
return -1;
if (!(xuser.userlevel & PERM_READMAIL))
return -3;
if (!title)
getdata(2, 0, "主題:", save_title, STRLEN - 20, DOECHO);
curredit |= EDIT_MAIL;
curredit &= ~EDIT_ITEM;
}
setutmpmode(SMAIL);
fpath[0] = '\0';
if (internet_mail) {
int res, ch;
if (vedit(fpath, NA, NULL) == -1) {
unlink(fpath);
clear();
return -2;
}
clear();
prints("信件即將寄給 %s\n標題為:%s\n確定要寄出嗎? (Y/N) [Y]",
userid, title);
ch = igetch();
switch (ch) {
case 'N':
case 'n':
outs("N\n信件已取消");
res = -2;
break;
default:
outs("Y\n請稍候, 信件傳遞中...\n");
res =
#ifndef USE_BSMTP
bbs_sendmail(fpath, title, userid);
#else
bsmtp(fpath, title, userid, 0);
#endif
hold_mail(fpath, userid);
}
unlink(fpath);
return res;
} else {
strlcpy(receiver, userid, sizeof(receiver));
sethomepath(genbuf, userid);
stampfile(genbuf, &mhdr);
strlcpy(mhdr.owner, cuser.userid, sizeof(mhdr.owner));
strncpy(mhdr.title, save_title, TTLEN);
if (vedit(genbuf, YEA, NULL) == -1) {
unlink(genbuf);
clear();
return -2;
}
clear();
sethomefile(fpath, userid, FN_OVERRIDES);
i = belong(fpath, cuser.userid);
sethomefile(fpath, userid, FN_REJECT);
if (i || !belong(fpath, cuser.userid)) { /* Ptt: 用belong有點討厭 */
sethomedir(fpath, userid);
if (append_record(fpath, &mhdr, sizeof(mhdr)) == -1)
return -1;
mailalert(userid);
}
hold_mail(genbuf, userid);
return 0;
}
}
void
my_send(char *uident)
{
switch (do_send(uident, NULL)) {
case -1:
outs(err_uid);
break;
case -2:
outs(msg_cancel);
break;
case -3:
prints("使用者 [%s] 無法收信", uident);
break;
}
pressanykey();
}
int
m_send()
{
char uident[40];
stand_title("且聽風的話");
usercomplete(msg_uid, uident);
showplans(uident);
if (uident[0])
my_send(uident);
return 0;
}
/* 群組寄信、回信 : multi_send, multi_reply */
static void
multi_list(int *reciper)
{
char uid[16];
char genbuf[200];
while (1) {
stand_title("群組寄信名單");
ShowNameList(3, 0, msg_cc);
move(1, 0);
prints("(I)引入好友 (O)引入上線通知 (N)引入新文章通知 (0-9)引入其他特別名單");
getdata(2, 0,
"(A)增加 (D)刪除 (M)確認寄信名單 (Q)取消 ?[M]",
genbuf, 4, LCECHO);
switch (genbuf[0]) {
case 'a':
while (1) {
move(1, 0);
usercomplete("請輸入要增加的代號(只按 ENTER 結束新增): ", uid);
if (uid[0] == '\0')
break;
move(2, 0);
clrtoeol();
if (!searchuser(uid))
outs(err_uid);
else if (!InNameList(uid)) {
AddNameList(uid);
(*reciper)++;
}
ShowNameList(3, 0, msg_cc);
}
break;
case 'd':
while (*reciper) {
move(1, 0);
namecomplete("請輸入要刪除的代號(只按 ENTER 結束刪除): ", uid);
if (uid[0] == '\0')
break;
if (RemoveNameList(uid))
(*reciper)--;
ShowNameList(3, 0, msg_cc);
}
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
listfile[5] = genbuf[0];
genbuf[0] = '1';
case 'i':
setuserfile(genbuf, genbuf[0] == '1' ? listfile : fn_overrides);
ToggleNameList(reciper, genbuf, msg_cc);
break;
case 'o':
setuserfile(genbuf, "alohaed");
ToggleNameList(reciper, genbuf, msg_cc);
break;
case 'n':
setuserfile(genbuf, "postlist");
ToggleNameList(reciper, genbuf, msg_cc);
break;
case 'q':
*reciper = 0;
return;
default:
return;
}
}
}
static void
multi_send(char *title)
{
FILE *fp;
struct word_t *p = NULL;
fileheader_t mymail;
char fpath[TTLEN], *ptr;
int reciper, listing;
char genbuf[256];
CreateNameList();
listing = reciper = 0;
if (*quote_file) {
AddNameList(quote_user);
reciper = 1;
fp = fopen(quote_file, "r");
assert(fp);
while (fgets(genbuf, 256, fp)) {
if (strncmp(genbuf, "※ ", 3)) {
if (listing)
break;
} else {
if (listing) {
strtok(ptr = genbuf + 3, " \n\r");
do {
if (searchuser(ptr) && !InNameList(ptr) &&
strcmp(cuser.userid, ptr)) {
AddNameList(ptr);
reciper++;
}
} while ((ptr = (char *)strtok(NULL, " \n\r")));
} else if (!strncmp(genbuf + 3, "[通告]", 6))
listing = 1;
}
}
fclose(fp);
ShowNameList(3, 0, msg_cc);
}
multi_list(&reciper);
move(1, 0);
clrtobot();
if (reciper) {
setutmpmode(SMAIL);
if (title)
do_reply_title(2, title);
else {
getdata(2, 0, "主題:", fpath, sizeof(fpath), DOECHO);
snprintf(save_title, sizeof(save_title), "[通告] %s", fpath);
}
setuserfile(fpath, fn_notes);
if ((fp = fopen(fpath, "w"))) {
fprintf(fp, "※ [通告] 共 %d 人收件", reciper);
listing = 80;
for (p = toplev; p; p = p->next) {
reciper = strlen(p->word) + 1;
if (listing + reciper > 75) {
listing = reciper;
fprintf(fp, "\n※");
} else
listing += reciper;
fprintf(fp, " %s", p->word);
}
memset(genbuf, '-', 75);
genbuf[75] = '\0';
fprintf(fp, "\n%s\n\n", genbuf);
fclose(fp);
}
curredit |= EDIT_LIST;
if (vedit(fpath, YEA, NULL) == -1) {
unlink(fpath);
curredit = 0;
outs(msg_cancel);
pressanykey();
return;
}
stand_title("寄信中...");
refresh();
listing = 80;
for (p = toplev; p; p = p->next) {
reciper = strlen(p->word) + 1;
if (listing + reciper > 75) {
listing = reciper;
outc('\n');
} else {
listing += reciper;
outc(' ');
}
outs(p->word);
if (searchuser(p->word) && strcmp(STR_GUEST, p->word))
sethomepath(genbuf, p->word);
else
continue;
stampfile(genbuf, &mymail);
unlink(genbuf);
Link(fpath, genbuf);
strlcpy(mymail.owner, cuser.userid, sizeof(mymail.owner));
strlcpy(mymail.title, save_title, sizeof(mymail.title));
mymail.filemode |= FILE_MULTI; /* multi-send flag */
sethomedir(genbuf, p->word);
if (append_record(genbuf, &mymail, sizeof(mymail)) == -1)
outs(err_uid);
mailalert(p->word);
}
hold_mail(fpath, NULL);
unlink(fpath);
curredit = 0;
} else
outs(msg_cancel);
pressanykey();
}
static int
multi_reply(int ent, fileheader_t * fhdr, char *direct)
{
if (!(fhdr->filemode & FILE_MULTI))
return mail_reply(ent, fhdr, direct);
stand_title("群組回信");
strlcpy(quote_user, fhdr->owner, sizeof(quote_user));
setuserfile(quote_file, fhdr->filename);
multi_send(fhdr->title);
return 0;
}
int
mail_list()
{
stand_title("群組作業");
multi_send(NULL);
return 0;
}
int
mail_all()
{
FILE *fp;
fileheader_t mymail;
char fpath[TTLEN];
char genbuf[200];
int i, unum;
char *userid;
stand_title("給所有使用者的系統通告");
setutmpmode(SMAIL);
getdata(2, 0, "主題:", fpath, sizeof(fpath), DOECHO);
snprintf(save_title, sizeof(save_title),
"[系統通告]\033[1;32m %s\033[m", fpath);
setuserfile(fpath, fn_notes);
if ((fp = fopen(fpath, "w"))) {
fprintf(fp, "※ [\033[1m系統通告\033[m] 這是封給所有使用者的信\n");
fprintf(fp, "-----------------------------------------------------"
"----------------------\n");
fclose(fp);
}
*quote_file = 0;
curredit |= EDIT_MAIL;
curredit &= ~EDIT_ITEM;
if (vedit(fpath, YEA, NULL) == -1) {
curredit = 0;
unlink(fpath);
outs(msg_cancel);
pressanykey();
return 0;
}
curredit = 0;
setutmpmode(MAILALL);
stand_title("寄信中...");
sethomepath(genbuf, cuser.userid);
stampfile(genbuf, &mymail);
unlink(genbuf);
Link(fpath, genbuf);
unlink(fpath);
strcpy(fpath, genbuf);
strlcpy(mymail.owner, cuser.userid, sizeof(mymail.owner)); /* 站長 ID */
strlcpy(mymail.title, save_title, sizeof(mymail.title));
sethomedir(genbuf, cuser.userid);
if (append_record(genbuf, &mymail, sizeof(mymail)) == -1)
outs(err_uid);
for (unum = SHM->number, i = 0; i < unum; i++) {
if (bad_user_id(SHM->userid[i]))
continue; /* Ptt */
userid = SHM->userid[i];
if (strcmp(userid, STR_GUEST) && strcmp(userid, "new") &&
strcmp(userid, cuser.userid)) {
sethomepath(genbuf, userid);
stampfile(genbuf, &mymail);
unlink(genbuf);
Link(fpath, genbuf);
strlcpy(mymail.owner, cuser.userid, sizeof(mymail.owner));
strlcpy(mymail.title, save_title, sizeof(mymail.title));
/* mymail.filemode |= FILE_MARKED; Ptt 公告改成不會mark */
sethomedir(genbuf, userid);
if (append_record(genbuf, &mymail, sizeof(mymail)) == -1)
outs(err_uid);
snprintf(genbuf, sizeof(genbuf),
"%*s %5d / %5d", IDLEN + 1, userid, i + 1, unum);
outmsg(genbuf);
refresh();
}
}
return 0;
}
int
mail_mbox()
{
char cmd[100];
fileheader_t fhdr;
snprintf(cmd, sizeof(cmd), "/tmp/%s.uu", cuser.userid);
snprintf(fhdr.title, sizeof(fhdr.title), "%s 私人資料", cuser.userid);
doforward(cmd, &fhdr, 'Z');
return 0;
}
static int
m_forward(int ent, fileheader_t * fhdr, char *direct)
{
char uid[STRLEN];
stand_title("轉達信件");
usercomplete(msg_uid, uid);
if (uid[0] == '\0')
return FULLUPDATE;
strlcpy(quote_user, fhdr->owner, sizeof(quote_user));
setuserfile(quote_file, fhdr->filename);
snprintf(save_title, sizeof(save_title), "%.64s (fwd)", fhdr->title);
move(1, 0);
clrtobot();
prints("轉信給: %s\n標 題: %s\n", uid, save_title);
switch (do_send(uid, save_title)) {
case -1:
outs(err_uid);
break;
case -2:
outs(msg_cancel);
break;
case -3:
prints("使用者 [%s] 無法收信", uid);
break;
}
pressanykey();
return FULLUPDATE;
}
static int delmsgs[128];
static int delcnt;
static int mrd;
static int
read_new_mail(fileheader_t * fptr)
{
static int idc;
char done = NA, delete_it;
char fname[256];
char genbuf[4];
if (fptr == NULL) {
delcnt = 0;
idc = 0;
return 0;
}
idc++;
if (fptr->filemode)
return 0;
clear();
move(10, 0);
prints("您要讀來自[%s]的訊息(%s)嗎?", fptr->owner, fptr->title);
getdata(11, 0, "請您確定(Y/N/Q)?[Y] ", genbuf, 3, DOECHO);
if (genbuf[0] == 'q')
return QUIT;
if (genbuf[0] == 'n')
return 0;
setuserfile(fname, fptr->filename);
fptr->filemode |= FILE_READ;
if (substitute_record(currmaildir, fptr, sizeof(*fptr), idc))
return -1;
mrd = 1;
delete_it = NA;
while (!done) {
int more_result = more(fname, YEA);
switch (more_result) {
case 1:
return READ_PREV;
case 2:
return RELATE_PREV;
case 3:
return READ_NEXT;
case 4:
return RELATE_NEXT;
case 5:
return RELATE_FIRST;
case 6:
return 0;
case 7:
mail_reply(idc, fptr, currmaildir);
return FULLUPDATE;
case 8:
multi_reply(idc, fptr, currmaildir);
return FULLUPDATE;
}
move(b_lines, 0);
clrtoeol();
outs(msg_mailer);
refresh();
switch (egetch()) {
case 'r':
case 'R':
mail_reply(idc, fptr, currmaildir);
break;
case 'x':
m_forward(idc, fptr, currmaildir);
break;
case 'y':
multi_reply(idc, fptr, currmaildir);
break;
case 'd':
case 'D':
delete_it = YEA;
default:
done = YEA;
}
}
if (delete_it) {
clear();
prints("刪除信件《%s》", fptr->title);
getdata(1, 0, msg_sure_ny, genbuf, 2, LCECHO);
if (genbuf[0] == 'y') {
unlink(fname);
delmsgs[delcnt++] = idc;
}
}
clear();
return 0;
}
int
m_new()
{
clear();
mrd = 0;
setutmpmode(RMAIL);
read_new_mail(NULL);
clear();
curredit |= EDIT_MAIL;
curredit &= ~EDIT_ITEM;
if (apply_record(currmaildir, read_new_mail, sizeof(fileheader_t)) == -1) {
outs("沒有新信件了");
pressanykey();
return -1;
}
curredit = 0;
if (delcnt) {
while (delcnt--)
delete_record(currmaildir, sizeof(fileheader_t), delmsgs[delcnt]);
}
outs(mrd ? "信已閱\畢" : "沒有新信件了");
pressanykey();
return -1;
}
static void
mailtitle()
{
char buf[256] = "";
showtitle("\0郵件選單", BBSName);
snprintf(buf, sizeof(buf),
"[←]離開[↑↓]選擇[→]閱\讀信件 [R]回信 [x]轉達 "
"[y]群組回信 [O]站外信:%s [h]求助\n\033[7m"
"編號 日 期 作 者 信 件 標 題 \033[32m",
HAS_PERM(PERM_NOOUTMAIL) ? "\033[31m關\033[m" : "開");
outs(buf);
buf[0] = 0;
if (mailsumlimit) {
snprintf(buf, sizeof(buf),
"(容量:%d/%dk %d/%d篇)", mailsum, mailsumlimit,
mailkeep, mailmaxkeep);
}
snprintf(buf, sizeof(buf), "%s%*s\033[m", buf, 29 - (int)strlen(buf), "");
outs(buf);
}
static void
maildoent(int num, fileheader_t * ent)
{
char *title, *mark, color, type = "+ Mm"[(ent->filemode & 3)];
if (TagNum && !Tagger(atoi(ent->filename + 2), 0, TAG_NIN))
type = 'D';
title = subject(mark = ent->title);
if (title == mark) {
color = '1';
mark = "◇";
} else {
color = '3';
mark = "R:";
}
if (strncmp(currtitle, title, TTLEN))
prints("%5d %c %-7s%-15.14s%s %.46s\n", num, type,
ent->date, ent->owner, mark, title);
else
prints("%5d %c %-7s%-15.14s\033[1;3%cm%s %.46s\033[0m\n", num, type,
ent->date, ent->owner, color, mark, title);
}
static int
m_idle(int ent, fileheader_t * fhdr, char *direct)
{
t_idle();
return FULLUPDATE;
}
static int
mail_del(int ent, fileheader_t * fhdr, char *direct)
{
char genbuf[200];
if (fhdr->filemode & FILE_MARKED)
return DONOTHING;
getdata(1, 0, msg_del_ny, genbuf, 3, LCECHO);
if (genbuf[0] == 'y') {
strlcpy(currfile, fhdr->filename, sizeof(currfile));
if (!delete_file(direct, sizeof(*fhdr), ent, cmpfilename)) {
setdirpath(genbuf, direct, fhdr->filename);
unlink(genbuf);
if ((currmode & MODE_SELECT)) {
int index;
sethomedir(genbuf, cuser.userid);
index = getindex(genbuf, fhdr->filename, sizeof(fileheader_t));
delete_file(genbuf, sizeof(fileheader_t), index, cmpfilename);
}
return DIRCHANGED;
}
}
return FULLUPDATE;
}
static int
mail_read(int ent, fileheader_t * fhdr, char *direct)
{
char buf[64];
char done, delete_it, replied;
clear();
setdirpath(buf, direct, fhdr->filename);
strncpy(currtitle, subject(fhdr->title), TTLEN);
done = delete_it = replied = NA;
while (!done) {
int more_result = more(buf, YEA);
if (more_result != -1) {
fhdr->filemode |= FILE_READ;
if ((currmode & MODE_SELECT)) {
int index;
index = getindex(currmaildir, fhdr->filename,
sizeof(fileheader_t));
substitute_record(currmaildir, fhdr, sizeof(*fhdr), index);
substitute_record(direct, fhdr, sizeof(*fhdr), ent);
} else
substitute_record(currmaildir, fhdr, sizeof(*fhdr), ent);
}
switch (more_result) {
case 1:
return READ_PREV;
case 2:
return RELATE_PREV;
case 3:
return READ_NEXT;
case 4:
return RELATE_NEXT;
case 5:
return RELATE_FIRST;
case 6:
return FULLUPDATE;
case 7:
mail_reply(ent, fhdr, direct);
return FULLUPDATE;
case 8:
multi_reply(ent, fhdr, direct);
return FULLUPDATE;
}
move(b_lines, 0);
clrtoeol();
refresh();
outs(msg_mailer);
switch (egetch()) {
case 'r':
case 'R':
replied = YEA;
mail_reply(ent, fhdr, direct);
break;
case 'x':
m_forward(ent, fhdr, direct);
break;
case 'y':
multi_reply(ent, fhdr, direct);
break;
case 'd':
delete_it = YEA;
default:
done = YEA;
}
}
if (delete_it)
mail_del(ent, fhdr, direct);
else {
fhdr->filemode |= FILE_READ;
#ifdef POSTBUG
if (replied)
bug_possible = YEA;
#endif
if ((currmode & MODE_SELECT)) {
int index;
index = getindex(currmaildir, fhdr->filename, sizeof(fileheader_t));
substitute_record(currmaildir, fhdr, sizeof(*fhdr), index);
substitute_record(direct, fhdr, sizeof(*fhdr), ent);
} else
substitute_record(currmaildir, fhdr, sizeof(*fhdr), ent);
#ifdef POSTBUG
bug_possible = NA;
#endif
}
return FULLUPDATE;
}
/* in boards/mail 回信給原作者,轉信站亦可 */
int
mail_reply(int ent, fileheader_t * fhdr, char *direct)
{
char uid[STRLEN];
char *t;
FILE *fp;
char genbuf[512];
stand_title("回 信");
/* 判斷是 boards 或 mail */
if (curredit & EDIT_MAIL)
setuserfile(quote_file, fhdr->filename);
else
setbfile(quote_file, currboard, fhdr->filename);
/* find the author */
strlcpy(quote_user, fhdr->owner, sizeof(quote_user));
if (strchr(quote_user, '.')) {
genbuf[0] = '\0';
if ((fp = fopen(quote_file, "r"))) {
fgets(genbuf, sizeof(genbuf), fp);
fclose(fp);
}
t = strtok(genbuf, str_space);
if (!strcmp(t, str_author1) || !strcmp(t, str_author2))
strlcpy(uid, strtok(NULL, str_space), sizeof(uid));
else {
outs("錯誤: 找不到作者。");
pressanykey();
return FULLUPDATE;
}
} else
strlcpy(uid, quote_user, sizeof(uid));
/* make the title */
do_reply_title(3, fhdr->title);
prints("\n收信人: %s\n標 題: %s\n", uid, save_title);
/* edit, then send the mail */
ent = curredit;
switch (do_send(uid, save_title)) {
case -1:
outs(err_uid);
break;
case -2:
outs(msg_cancel);
break;
case -3:
prints("使用者 [%s] 無法收信", uid);
break;
}
curredit = ent;
pressanykey();
return FULLUPDATE;
}
static int
mail_edit(int ent, fileheader_t * fhdr, char *direct)
{
char genbuf[200];
if (!HAS_PERM(PERM_SYSOP) &&
strcmp(cuser.userid, fhdr->owner) &&
strcmp("[備.忘.錄]", fhdr->owner))
return DONOTHING;
setdirpath(genbuf, direct, fhdr->filename);
vedit(genbuf, NA, NULL);
return FULLUPDATE;
}
static int
mail_nooutmail(int ent, fileheader_t * fhdr, char *direct)
{
cuser.userlevel ^= PERM_NOOUTMAIL;
passwd_update(usernum, &cuser);
return FULLUPDATE;
}
static int
mail_mark(int ent, fileheader_t * fhdr, char *direct)
{
fhdr->filemode ^= FILE_MARKED;
if ((currmode & MODE_SELECT)) {
int index;
index = getindex(currmaildir, fhdr->filename, sizeof(fileheader_t));
substitute_record(currmaildir, fhdr, sizeof(*fhdr), index);
substitute_record(direct, fhdr, sizeof(*fhdr), ent);
} else
substitute_record(currmaildir, fhdr, sizeof(*fhdr), ent);
return PART_REDRAW;
}
/* help for mail reading */
static char *mail_help[] = {
"\0電子信箱操作說明",
"\01基本命令",
"(p)(↑) 前一篇文章",
"(n)(↓) 下一篇文章",
"(P)(PgUp) 前一頁",
"(N)(PgDn) 下一頁",
"(##)(cr) 跳到第 ## 筆",
"($) 跳到最後一筆",
"\01進階命令",
"(r)(→)/(R)讀信 / 回信",
"(O) 關閉/開啟 站外信件轉入",
"(c/z) 收入此信件進入私人信件夾/進入私人信件夾",
"(x/X) 轉達信件/轉錄文章到其他看板",
"(y) 群組回信",
"(F) 將信傳送回您的電子信箱 (u)水球整理寄回信箱",
"(d) 殺掉此信",
"(D) 殺掉指定範圍的信",
"(m) 將信標記,以防被清除",
"(^G) 立即重建信箱 (信箱毀損時用)",
"(t) 標記欲刪除信件",
"(^D) 刪除已標記信件",
NULL
};
static int
m_help()
{
show_help(mail_help);
return FULLUPDATE;
}
static int
mail_cross_post(int ent, fileheader_t * fhdr, char *direct)
{
char xboard[20], fname[80], xfpath[80], xtitle[80], inputbuf[10];
fileheader_t xfile;
FILE *xptr;
int author = 0;
char genbuf[200];
char genbuf2[4];
move(2, 0);
clrtoeol();
move(3, 0);
clrtoeol();
move(1, 0);
generalnamecomplete("轉錄本文章於看板:", xboard, sizeof(xboard),
SHM->Bnumber,
completeboard_compar,
completeboard_permission,
completeboard_getname);
if (*xboard == '\0' || !haspostperm(xboard))
return FULLUPDATE;
ent = 1;
if (HAS_PERM(PERM_SYSOP) || !strcmp(fhdr->owner, cuser.userid)) {
getdata(2, 0, "(1)原文轉載 (2)舊轉錄格式?[1] ",
genbuf, 3, DOECHO);
if (genbuf[0] != '2') {
ent = 0;
getdata(2, 0, "保留原作者名稱嗎?[Y] ", inputbuf, 3, DOECHO);
if (inputbuf[0] != 'n' && inputbuf[0] != 'N')
author = 1;
}
}
if (ent)
snprintf(xtitle, sizeof(xtitle), "[轉錄]%.66s", fhdr->title);
else
strlcpy(xtitle, fhdr->title, sizeof(xtitle));
snprintf(genbuf, sizeof(genbuf), "採用原標題《%.60s》嗎?[Y] ", xtitle);
getdata(2, 0, genbuf, genbuf2, sizeof(genbuf2), LCECHO);
if (*genbuf2 == 'n')
if (getdata(2, 0, "標題:", genbuf, TTLEN, DOECHO))
strlcpy(xtitle, genbuf, sizeof(xtitle));
getdata(2, 0, "(S)存檔 (L)站內 (Q)取消?[Q] ", genbuf, 3, LCECHO);
if (genbuf[0] == 'l' || genbuf[0] == 's') {
int currmode0 = currmode;
currmode = 0;
setbpath(xfpath, xboard);
stampfile(xfpath, &xfile);
if (author)
strlcpy(xfile.owner, fhdr->owner, sizeof(xfile.owner));
else
strlcpy(xfile.owner, cuser.userid, sizeof(xfile.owner));
strlcpy(xfile.title, xtitle, sizeof(xfile.title));
if (genbuf[0] == 'l') {
xfile.filemode = FILE_LOCAL;
}
setuserfile(fname, fhdr->filename);
if (ent) {
xptr = fopen(xfpath, "w");
assert(xptr);
strlcpy(save_title, xfile.title, sizeof(save_title));
strlcpy(xfpath, currboard, sizeof(xfpath));
strlcpy(currboard, xboard, sizeof(currboard));
write_header(xptr);
strlcpy(currboard, xfpath, sizeof(currboard));
fprintf(xptr, "※ [本文轉錄自 %s 信箱]\n\n", cuser.userid);
b_suckinfile(xptr, fname);
addsignature(xptr, 0);
fclose(xptr);
} else {
unlink(xfpath);
Link(fname, xfpath);
}
setbdir(fname, xboard);
append_record(fname, &xfile, sizeof(xfile));
setbtotal(getbnum(xboard));
if (!xfile.filemode)
outgo_post(&xfile, xboard);
cuser.numposts++;
passwd_update(usernum, &cuser);
outs("文章轉錄完成");
pressanykey();
currmode = currmode0;
}
return FULLUPDATE;
}
int
mail_man()
{
char buf[64], buf1[64];
if (HAS_PERM(PERM_MAILLIMIT)) {
int mode0 = currutmp->mode;
int stat0 = currstat;
sethomeman(buf, cuser.userid);
snprintf(buf1, sizeof(buf1), "%s 的信件夾", cuser.userid);
a_menu(buf1, buf, 1);
currutmp->mode = mode0;
currstat = stat0;
return FULLUPDATE;
}
return DONOTHING;
}
static int
mail_cite(int ent, fileheader_t * fhdr, char *direct)
{
char fpath[256];
char title[TTLEN + 1];
static char xboard[20];
char buf[20];
int bid;
setuserfile(fpath, fhdr->filename);
strlcpy(title, "◇ ", sizeof(title));
strncpy(title + 3, fhdr->title, TTLEN - 3);
title[TTLEN] = '\0';
a_copyitem(fpath, title, 0, 1);
if (cuser.userlevel >= PERM_BM) {
move(2, 0);
clrtoeol();
move(3, 0);
clrtoeol();
move(1, 0);
generalnamecomplete("輸入看板名稱 (直接Enter進入私人信件夾):",
buf, sizeof(buf),
SHM->Bnumber,
completeboard_compar,
completeboard_permission,
completeboard_getname);
if (*buf)
strlcpy(xboard, buf, sizeof(xboard));
if (*xboard && ((bid = getbnum(xboard)) >= 0)){ /* XXXbid */
setapath(fpath, xboard);
setutmpmode(ANNOUNCE);
a_menu(xboard, fpath, HAS_PERM(PERM_ALLBOARD) ? 2 :
is_BM_cache(bid) ? 1 : 0);
} else {
mail_man();
}
return FULLUPDATE;
} else {
mail_man();
return FULLUPDATE;
}
}
static int
mail_save(int ent, fileheader_t * fhdr, char *direct)
{
char fpath[256];
char title[TTLEN + 1];
if (HAS_PERM(PERM_MAILLIMIT)) {
setuserfile(fpath, fhdr->filename);
strlcpy(title, "◇ ", sizeof(title));
strncpy(title + 3, fhdr->title, TTLEN - 3);
title[TTLEN] = '\0';
a_copyitem(fpath, title, fhdr->owner, 1);
sethomeman(fpath, cuser.userid);
a_menu(cuser.userid, fpath, 1);
return FULLUPDATE;
}
return DONOTHING;
}
#ifdef OUTJOBSPOOL
static int
mail_waterball(int ent, fileheader_t * fhdr, char *direct)
{
static char address[60], cmode = 1;
char fname[500], genbuf[200];
FILE *fp;
if (!(strstr(fhdr->title, "熱線") && strstr(fhdr->title, "記錄"))) {
vmsg("必須是 熱線記錄 才能使用水球整理的唷!");
return 1;
}
if (!address[0])
strlcpy(address, cuser.email, sizeof(address));
move(b_lines - 8, 0);
outs("水球整理程式:\n"
"系統將會按照和不同人丟的水球各自獨立\n"
"於整點的時候 (尖鋒時段除外) 將資料整理好寄送給您\n\n\n");
if (address[0]) {
snprintf(genbuf, sizeof(genbuf), "寄給 [%s] 嗎(Y/N/Q)?[Y] ", address);
getdata(b_lines - 5, 0, genbuf, fname, 3, LCECHO);
if (fname[0] == 'q') {
outmsg("取消處理");
return 1;
}
if (fname[0] == 'n')
address[0] = '\0';
}
if (!address[0]) {
getdata(b_lines - 5, 0, "請輸入郵件地址:", fname, 60, DOECHO);
if (fname[0] && strchr(fname, '.')) {
strlcpy(address, fname, sizeof(address));
} else {
vmsg("取消處理");
return 1;
}
}
if (invalidaddr(address))
return -2;
if( strstr(address, ".bbs") && HAS_PERM(PERM_NOOUTMAIL) ){
move(b_lines - 4, 0);
outs("\n您必須要打開接受站外信, 水球整理系統才能寄入結果\n"
"請麻煩到【郵件選單】按大寫 O改成接受站外信 (在右上角)\n"
"再重新執行本功\能 :)\n");
vmsg("請打開站外信, 再重新執行本功\能");
return FULLUPDATE;
}
//snprintf(fname, sizeof(fname), "%d\n", cmode);
move(b_lines - 4, 0);
outs("系統提供兩種模式: \n"
"模式 0: 精簡模式, 將不含顏色控制碼, 方便以純文字編輯器整理收藏\n"
"模式 1: 華麗模式, 包含顏色控制碼等, 方便在 bbs上直接編輯收藏\n");
getdata(b_lines - 1, 0, "使用模式(0/1/Q)? [1]", fname, 3, LCECHO);
if (fname[0] == 'Q' || fname[0] == 'q') {
outmsg("取消處理");
return 1;
}
cmode = (fname[0] != '0' && fname[0] != '1') ? 1 : fname[0] - '0';
snprintf(fname, sizeof(fname), BBSHOME "/jobspool/water.src.%s-%d",
cuser.userid, (int)now);
snprintf(genbuf, sizeof(genbuf), "cp " BBSHOME "/home/%c/%s/%s %s",
cuser.userid[0], cuser.userid, fhdr->filename, fname);
system(genbuf);
/* dirty code ;x */
snprintf(fname, sizeof(fname), BBSHOME "/jobspool/water.des.%s-%d",
cuser.userid, (int)now);
fp = fopen(fname, "wt");
assert(fp);
fprintf(fp, "%s\n%s\n%d\n", cuser.userid, address, cmode);
fclose(fp);
vmsg("設定完成, 系統將在下一個整點(尖鋒時段除外)將資料寄給您");
return FULLUPDATE;
}
#endif
static struct onekey_t mail_comms[] = {
{'z', mail_man},
{'c', mail_cite},
{'s', mail_save},
{'d', mail_del},
{'D', del_range},
{'r', mail_read},
{'R', mail_reply},
{'E', mail_edit},
{'m', mail_mark},
{'O', mail_nooutmail},
{'T', edit_title},
{'x', m_forward},
{'X', mail_cross_post},
{Ctrl('G'), built_mail_index}, /* 修信箱 */
{'y', multi_reply},
{Ctrl('I'), m_idle},
{'h', m_help},
#ifdef OUTJOBSPOOL
{'u', mail_waterball},
#endif
{'\0', NULL}
};
int
m_read()
{
if (get_num_records(currmaildir, sizeof(fileheader_t))) {
curredit = EDIT_MAIL;
curredit &= ~EDIT_ITEM;
i_read(RMAIL, currmaildir, mailtitle, maildoent, mail_comms, -1);
curredit = 0;
currutmp->mailalert = load_mailalert(cuser.userid);
return 0;
} else {
outs("您沒有來信");
return XEASY;
}
}
/* 寄站內信 */
static int
send_inner_mail(char *fpath, char *title, char *receiver)
{
char genbuf[256];
fileheader_t mymail;
if (!searchuser(receiver))
return -2;
sethomepath(genbuf, receiver);
stampfile(genbuf, &mymail);
if (!strcmp(receiver, cuser.userid)) {
strlcpy(mymail.owner, "[" BBSNAME "]", sizeof(mymail.owner));
mymail.filemode = FILE_READ;
} else
strlcpy(mymail.owner, cuser.userid, sizeof(mymail.owner));
strncpy(mymail.title, title, TTLEN);
unlink(genbuf);
Link(fpath, genbuf);
sethomedir(genbuf, receiver);
return do_append(genbuf, &mymail, sizeof(mymail));
}
#include <netdb.h>
#include <pwd.h>
#include <time.h>
#ifndef USE_BSMTP
static int
bbs_sendmail(char *fpath, char *title, char *receiver)
{
char *ptr;
char genbuf[256];
FILE *fin, *fout;
/* 中途攔截 */
if ((ptr = strchr(receiver, ';'))) {
*ptr = '\0';
}
if ((ptr = strstr(receiver, str_mail_address)) || !strchr(receiver, '@')) {
char hacker[20];
int len;
if (strchr(receiver, '@')) {
len = ptr - receiver;
memcpy(hacker, receiver, len);
hacker[len] = '\0';
} else
strlcpy(hacker, receiver, sizeof(hacker));
return send_inner_mail(fpath, title, hacker);
}
/* Running the sendmail */
if (fpath == NULL) {
snprintf(genbuf, sizeof(genbuf),
"/usr/sbin/sendmail %s > /dev/null", receiver);
fin = fopen("etc/confirm", "r");
} else {
snprintf(genbuf, sizeof(genbuf),
"/usr/sbin/sendmail -f %s%s %s > /dev/null",
cuser.userid, str_mail_address, receiver);
fin = fopen(fpath, "r");
}
fout = popen(genbuf, "w");
if (fin == NULL || fout == NULL) // XXX no fclose() if only one fopen succeed
return -1;
if (fpath)
fprintf(fout, "Reply-To: %s%s\nFrom: %s%s\n",
cuser.userid, str_mail_address, cuser.userid,
str_mail_address);
fprintf(fout, "To: %s\nSubject: %s\n", receiver, title);
fprintf(fout, "X-Disclaimer: " BBSNAME "對本信內容恕不負責。\n\n");
while (fgets(genbuf, 255, fin)) {
if (genbuf[0] == '.' && genbuf[1] == '\n')
fputs(". \n", fout);
else
fputs(genbuf, fout);
}
fclose(fin);
fprintf(fout, ".\n");
pclose(fout);
return 0;
}
#else /* USE_BSMTP */
int
bsmtp(char *fpath, char *title, char *rcpt, int method)
{
char buf[80], *ptr;
time_t chrono;
MailQueue mqueue;
/* check if the mail is a inner mail */
if ((ptr = strstr(rcpt, str_mail_address)) || !strchr(rcpt, '@')) {
char hacker[20];
int len;
if (strchr(rcpt, '@')) {
len = ptr - rcpt;
memcpy(hacker, rcpt, len);
hacker[len] = '\0';
} else
strlcpy(hacker, rcpt, sizeof(hacker));
return send_inner_mail(fpath, title, hacker);
}
chrono = now;
if (method != MQ_JUSTIFY) { /* 認證信 */
/* stamp the queue file */
strlcpy(buf, "out/", sizeof(buf));
for (;;) {
snprintf(buf + 4, sizeof(buf) - 4, "M.%ld.A", ++chrono);
if (!dashf(buf)) {
Link(fpath, buf);
break;
}
}
fpath = buf;
strlcpy(mqueue.filepath, fpath, sizeof(mqueue.filepath));
strlcpy(mqueue.subject, title, sizeof(mqueue.subject));
}
/* setup mail queue */
mqueue.mailtime = chrono;
mqueue.method = method;
strlcpy(mqueue.sender, cuser.userid, sizeof(mqueue.sender));
strlcpy(mqueue.username, cuser.username, sizeof(mqueue.username));
strlcpy(mqueue.rcpt, rcpt, sizeof(mqueue.rcpt));
if (do_append("out/.DIR", (fileheader_t *) & mqueue, sizeof(mqueue)) < 0)
return 0;
return chrono;
}
#endif /* USE_BSMTP */
int
doforward(char *direct, fileheader_t * fh, int mode)
{
static char address[60];
char fname[500];
int return_no;
char genbuf[200];
if (!address[0])
strlcpy(address, cuser.email, sizeof(address));
if (address[0]) {
snprintf(genbuf, sizeof(genbuf),
"確定轉寄給 [%s] 嗎(Y/N/Q)?[Y] ", address);
getdata(b_lines - 1, 0, genbuf, fname, 3, LCECHO);
if (fname[0] == 'q') {
outmsg("取消轉寄");
return 1;
}
if (fname[0] == 'n')
address[0] = '\0';
}
if (!address[0]) {
do {
getdata(b_lines - 1, 0, "請輸入轉寄地址:", fname, 60, DOECHO);
if (fname[0]) {
if (strchr(fname, '.'))
strlcpy(address, fname, sizeof(address));
else
snprintf(address, sizeof(address),
"%s.bbs@%s", fname, MYHOSTNAME);
} else {
outmsg("取消轉寄");
return 1;
}
} while (mode == 'Z' && strstr(address, MYHOSTNAME));
}
if (invalidaddr(address))
return -2;
snprintf(fname, sizeof(fname), "正轉寄給 %s, 請稍候...", address);
outmsg(fname);
move(b_lines - 1, 0);
refresh();
/* 追蹤使用者 */
if (HAS_PERM(PERM_LOGUSER)) {
char msg[200];
snprintf(msg, sizeof(msg), "%s mailforward to %s at %s",
cuser.userid, address, Cdate(&now));
log_user(msg);
}
if (mode == 'Z') {
snprintf(fname, sizeof(fname),
TAR_PATH " cfz /tmp/home.%s.tgz home/%c/%s; "
MUTT_PATH " -a /tmp/home.%s.tgz -s 'home.%s.tgz' '%s' </dev/null;"
"rm /tmp/home.%s.tgz",
cuser.userid, cuser.userid[0], cuser.userid,
cuser.userid, cuser.userid, address, cuser.userid);
system(fname);
return 0;
snprintf(fname, sizeof(fname), TAR_PATH " cfz - home/%c/%s | "
"/usr/bin/uuencode %s.tgz > %s",
cuser.userid[0], cuser.userid, cuser.userid, direct);
system(fname);
strlcpy(fname, direct, sizeof(fname));
} else if (mode == 'U') {
char tmp_buf[128];
snprintf(fname, sizeof(fname), "/tmp/bbs.uu%05d", currpid);
snprintf(tmp_buf, sizeof(tmp_buf),
"/usr/bin/uuencode %s/%s uu.%05d > %s",
direct, fh->filename, currpid, fname);
system(tmp_buf);
} else if (mode == 'F') {
char tmp_buf[128];
snprintf(fname, sizeof(fname), "/tmp/bbs.f%05d", currpid);
snprintf(tmp_buf, sizeof(tmp_buf),
"cp %s/%s %s", direct, fh->filename, fname);
system(tmp_buf);
} else
return -1;
return_no =
#ifndef USE_BSMTP
bbs_sendmail(fname, fh->title, address);
#else
bsmtp(fname, fh->title, address, mode);
#endif
unlink(fname);
return (return_no);
}
int
load_mailalert(char *userid)
{
struct stat st;
char maildir[256];
int fd;
register int numfiles;
fileheader_t my_mail;
sethomedir(maildir, userid);
if (!HAS_PERM(PERM_BASIC))
return 0;
if (stat(maildir, &st) < 0)
return 0;
numfiles = st.st_size / sizeof(fileheader_t);
if (numfiles <= 0)
return 0;
/* 看看有沒有信件還沒讀過?從檔尾回頭檢查,效率較高 */
if ((fd = open(maildir, O_RDONLY)) > 0) {
lseek(fd, st.st_size - sizeof(fileheader_t), SEEK_SET);
while (numfiles--) {
read(fd, &my_mail, sizeof(fileheader_t));
if (!(my_mail.filemode & FILE_READ)) {
close(fd);
return 1;
}
lseek(fd, -(off_t) 2 * sizeof(fileheader_t), SEEK_CUR);
}
close(fd);
}
return 0;
}
#ifdef EMAIL_JUSTIFY
static void
mail_justify(userec_t muser)
{
fileheader_t mhdr;
char title[128], buf1[80];
FILE *fp;
sethomepath(buf1, muser.userid);
stampfile(buf1, &mhdr);
unlink(buf1);
strlcpy(mhdr.owner, cuser.userid, sizeof(mhdr.owner));
strncpy(mhdr.title, "[審核通過]", TTLEN);
mhdr.filemode = 0;
if (valid_ident(muser.email) && !invalidaddr(muser.email)) {
char title[80], *ptr;
unsigned short checksum; /* 16-bit is enough */
char ch;
checksum = searchuser(muser.userid);
ptr = muser.email;
while ((ch = *ptr++)) {
if (ch <= ' ')
break;
if (ch >= 'A' && ch <= 'Z')
ch |= 0x20;
checksum = (checksum << 1) ^ ch;
}
snprintf(title, sizeof(title), "[PTT BBS]To %s(%d:%d) [User Justify]",
muser.userid, getuser(muser.userid) + MAGIC_KEY, checksum);
if (
#ifndef USE_BSMTP
bbs_sendmail(NULL, title, muser.email)
#else
bsmtp(NULL, title, muser.email, MQ_JUSTIFY);
#endif
<0)
Link("etc/bademail", buf1);
else
Link("etc/replyemail", buf1);
} else
Link("etc/bademail", buf1);
sethomedir(title, muser.userid);
append_record(title, &mhdr, sizeof(mhdr));
}
#endif /* EMAIL_JUSTIFY */