Newer
Older
truncstr(toaddr,"> ");
if((p=strrchr(toaddr,'@'))!=NULL && (tp=strrchr(toaddr,':'))!=NULL
&& tp > p)
*tp=0; /* Remove ":port" designation from envelope */
}
sockprintf(sock,"RCPT TO: <%s>", toaddr);
if(!sockgetrsp(sock,"25", buf, sizeof(buf))) {
remove_msg_intransit(&smb,&msg);
SAFEPRINTF3(err,badrsp_err,server,buf,"25*");
bounce(&smb,&msg,err,buf[0]=='5');
continue;
}
/* DATA */
sockprintf(sock,"DATA");
if(!sockgetrsp(sock,"354", buf, sizeof(buf))) {
remove_msg_intransit(&smb,&msg);
SAFEPRINTF3(err,badrsp_err,server,buf,"354");
bounce(&smb,&msg,err,buf[0]=='5');
continue;
}
lprintf(LOG_DEBUG,"%04d SEND sending message text (%u bytes)"
lines=sockmsgtxt(sock,&msg,msgtxt,-1);
if(!sockgetrsp(sock,"250", buf, sizeof(buf))) {
remove_msg_intransit(&smb,&msg);
SAFEPRINTF3(err,badrsp_err,server,buf,"250");
bounce(&smb,&msg,err,buf[0]=='5');
continue;
}
lprintf(LOG_DEBUG,"%04d SEND message transfer complete (%lu lines)", sock, lines);
/* Now lets mark this message for deletion without corrupting the index */
msg.hdr.attr|=MSG_DELETE;
msg.hdr.netattr&=~MSG_INTRANSIT;
if((i=smb_updatemsg(&smb,&msg))!=SMB_SUCCESS)
lprintf(LOG_ERR,"%04d !SEND ERROR %d (%s) deleting message #%lu"
,sock, i, smb.last_error, msg.hdr.number);
if(msg.hdr.auxattr&MSG_FILEATTACH)
delfattach(&scfg,&msg);
/* QUIT */
sockprintf(sock,"QUIT");
sockgetrsp(sock,"221", buf, sizeof(buf));

rswindell
committed
mail_close_socket(sock);
sock=INVALID_SOCKET;
}
status(STATUS_WFC);
/* Free up resources here */
if(mail!=NULL)
freemail(mail);
}
if(sock!=INVALID_SOCKET)

rswindell
committed
mail_close_socket(sock);
smb_freemsgtxt(msgtxt);
smb_freemsgmem(&msg);
smb_close(&smb);
if(active_sendmail!=0)
active_sendmail=0, update_clients();
thread_down();
lprintf(LOG_DEBUG,"0000 SendMail thread terminated (%u threads remain)", thread_count);
sendmail_running=FALSE;
}
void DLLCALL mail_terminate(void)
lprintf(LOG_DEBUG,"%04d Mail Server terminate",server_socket);
terminate_server=TRUE;
}
static void cleanup(int code)
{
int i;
semfile_list_free(&recycle_semfiles);
semfile_list_free(&shutdown_semfiles);
if(mailproc_list!=NULL) {
for(i=0;i<mailproc_count;i++)
strListFree(&mailproc_list[i].to);
FREE_AND_NULL(mailproc_list);
}
if(server_socket!=INVALID_SOCKET) {

rswindell
committed
mail_close_socket(server_socket);
server_socket=INVALID_SOCKET;
}
if(submission_socket!=INVALID_SOCKET) {
mail_close_socket(submission_socket);
submission_socket=INVALID_SOCKET;
}
if(pop3_socket!=INVALID_SOCKET) {

rswindell
committed
mail_close_socket(pop3_socket);
pop3_socket=INVALID_SOCKET;
}
update_clients();
#ifdef _WINSOCKAPI_
if(WSAInitialized && WSACleanup()!=0)
lprintf(LOG_ERR,"0000 !WSACleanup ERROR %d",ERROR_VALUE);
thread_down();
if(terminate_server || code)
lprintf(LOG_INFO,"#### Mail Server thread terminated (%u threads remain, %lu clients served)"
,thread_count, served);
if(startup!=NULL && startup->terminated!=NULL)
startup->terminated(startup->cbdata,code);
const char* DLLCALL mail_ver(void)
{
static char ver[256];
char compiler[32];
sscanf("$Revision$", "%*s %s", revision);
sprintf(ver,"Synchronet Mail Server %s%s SMBLIB %s "
#ifdef _DEBUG
," Debug"
#else
,""
#endif
,smb_lib_ver()
,__DATE__, __TIME__, compiler
);
return(ver);
}
void DLLCALL mail_server(void* arg)
char* p;
char path[MAX_PATH+1];
char str[256];
char error[256];
char compiler[32];
SOCKADDR_IN server_addr;
SOCKADDR_IN client_addr;
socklen_t client_addr_len;
SOCKET client_socket;
int i;
int result;
ulong l;
time_t t;
time_t start;
time_t initialized=0;
SOCKET high_socket_set;
pop3_t* pop3;
smtp_t* smtp;

rswindell
committed
struct timeval tv;
FILE* fp;
str_list_t sec_list;
startup=(mail_startup_t*)arg;
#ifdef _THREAD_SUID_BROKEN
if(thread_suid_broken)
startup->seteuid(TRUE);
#endif
sbbs_beep(100,500);
fprintf(stderr, "No startup structure passed!\n");
return;
}
if(startup->size!=sizeof(mail_startup_t)) { /* verify size */
sbbs_beep(100,500);
sbbs_beep(300,500);
sbbs_beep(100,500);
fprintf(stderr, "Invalid startup structure!\n");
return;
}
/* Setup intelligent defaults */
if(startup->relay_port==0) startup->relay_port=IPPORT_SMTP;
if(startup->submission_port==0) startup->submission_port=IPPORT_SUBMISSION;
if(startup->smtp_port==0) startup->smtp_port=IPPORT_SMTP;
if(startup->pop3_port==0) startup->pop3_port=IPPORT_POP3;
if(startup->rescan_frequency==0) startup->rescan_frequency=3600; /* 60 minutes */
if(startup->max_delivery_attempts==0) startup->max_delivery_attempts=50;
if(startup->max_inactivity==0) startup->max_inactivity=120; /* seconds */
if(startup->max_recipients==0) startup->max_recipients=100;
if(startup->sem_chk_freq==0) startup->sem_chk_freq=2;

rswindell
committed
#ifdef JAVASCRIPT
if(startup->js.max_bytes==0) startup->js.max_bytes=JAVASCRIPT_MAX_BYTES;
if(startup->js.cx_stack==0) startup->js.cx_stack=JAVASCRIPT_CONTEXT_STACK;
#endif
uptime=0;
served=0;
startup->recycle_now=FALSE;
startup->shutdown_now=FALSE;
terminate_server=FALSE;
SetThreadName("Mail Server");
do {
thread_up(FALSE /* setuid */);
status("Initializing");
memset(&scfg, 0, sizeof(scfg));
lprintf(LOG_INFO,"Synchronet Mail Server Revision %s%s"
," Debug"
lprintf(LOG_INFO,"Compiled %s %s with %s", __DATE__, __TIME__, compiler);
lprintf(LOG_INFO,"SMBLIB %s (format %x.%02x)",smb_lib_ver(),smb_ver()>>8,smb_ver()&0xff);
sbbs_srand();
if(!winsock_startup()) {

rswindell
committed
cleanup(1);
return;
}
t=time(NULL);
lprintf(LOG_INFO,"Initializing on %.24s with options: %lx"
,CTIME_R(&t,str),startup->options);
/* Initial configuration and load from CNF files */
SAFECOPY(scfg.ctrl_dir,startup->ctrl_dir);
lprintf(LOG_INFO,"Loading configuration files from %s", scfg.ctrl_dir);
scfg.size=sizeof(scfg);
SAFECOPY(error,UNKNOWN_LOAD_ERROR);
if(!load_cfg(&scfg, NULL, TRUE, error)) {
lprintf(LOG_ERR,"!ERROR %s",error);
lprintf(LOG_ERR,"!Failed to load configuration files");
cleanup(1);
return;
}
if(startup->temp_dir[0])
SAFECOPY(scfg.temp_dir,startup->temp_dir);
else
SAFECOPY(scfg.temp_dir,"../temp");
prep_dir(scfg.ctrl_dir, scfg.temp_dir, sizeof(scfg.temp_dir));
MKDIR(scfg.temp_dir);
lprintf(LOG_DEBUG,"Temporary file directory: %s", scfg.temp_dir);
if(!isdir(scfg.temp_dir)) {
lprintf(LOG_ERR,"!Invalid temp directory: %s", scfg.temp_dir);
cleanup(1);
return;
}
/* Parse the mailproc[.host].ini */
mailproc_list=NULL;
mailproc_count=0;
iniFileName(path,sizeof(path),scfg.ctrl_dir,"mailproc.ini");
if((fp=iniOpenFile(path, /* create? */FALSE))!=NULL) {
lprintf(LOG_DEBUG,"Reading %s",path);
sec_list = iniReadSectionList(fp,/* prefix */NULL);
if((mailproc_count=strListCount(sec_list))!=0
&& (mailproc_list=malloc(mailproc_count*sizeof(struct mailproc)))!=NULL) {
for(i=0;i<mailproc_count;i++) {
memset(&mailproc_list[i],0,sizeof(struct mailproc));
SAFECOPY(mailproc_list[i].cmdline,sec_list[i]);
mailproc_list[i].to =
iniReadStringList(fp,sec_list[i],"to",",",NULL);
mailproc_list[i].passthru =
iniReadBool(fp,sec_list[i],"passthru",TRUE);
mailproc_list[i].native =
iniReadBool(fp,sec_list[i],"native",FALSE);
}
}
iniFreeStringList(sec_list);
iniCloseFile(fp);
}
if(startup->host_name[0]==0)
SAFECOPY(startup->host_name,scfg.sys_inetaddr);
if(!(scfg.sys_misc&SM_LOCAL_TZ) && !(startup->options&MAIL_OPT_LOCAL_TIMEZONE)) {
lprintf(LOG_ERR,"!putenv() FAILED");
tzset();
if((t=checktime())!=0) { /* Check binary time */
lprintf(LOG_ERR,"!TIME PROBLEM (%ld)",t);
cleanup(1);
return;
}
}
if(uptime==0)
uptime=time(NULL); /* this must be done *after* setting the timezone */
if(startup->max_clients==0) {
startup->max_clients=scfg.sys_nodes;
if(startup->max_clients<10)
startup->max_clients=10;
}
lprintf(LOG_DEBUG,"Maximum clients: %u",startup->max_clients);
lprintf(LOG_DEBUG,"Maximum inactivity: %u seconds",startup->max_inactivity);
active_clients=0,update_clients();
/* open a socket and wait for a client */
server_socket = mail_open_socket(SOCK_STREAM,"smtp");
if(server_socket == INVALID_SOCKET) {
lprintf(LOG_ERR,"!ERROR %d opening socket", ERROR_VALUE);
cleanup(1);
return;
}
lprintf(LOG_DEBUG,"%04d SMTP socket opened",server_socket);
/*****************************/
/* Listen for incoming calls */
/*****************************/
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_addr.s_addr = htonl(startup->interface_addr);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(startup->smtp_port);
if(startup->smtp_port < IPPORT_RESERVED) {
if(startup->seteuid!=NULL)
startup->seteuid(FALSE);
}
result = retry_bind(server_socket,(struct sockaddr *)&server_addr,sizeof(server_addr)
,startup->bind_retry_count,startup->bind_retry_delay,"SMTP Server",lprintf);
if(startup->smtp_port < IPPORT_RESERVED) {
if(startup->seteuid!=NULL)
startup->seteuid(TRUE);
}
lprintf(LOG_ERR,"%04d %s",server_socket, BIND_FAILURE_HELP);
cleanup(1);
return;
}
lprintf(LOG_DEBUG,"%04d SMTP socket bound to port %u"
,server_socket, startup->smtp_port);
result = listen(server_socket, 1);
lprintf(LOG_ERR,"%04d !ERROR %d (%d) listening on socket"
,server_socket, result, ERROR_VALUE);
cleanup(1);
return;
}
4378
4379
4380
4381
4382
4383
4384
4385
4386
4387
4388
4389
4390
4391
4392
4393
4394
4395
4396
4397
4398
4399
4400
4401
4402
4403
4404
4405
4406
4407
4408
4409
4410
4411
4412
4413
4414
4415
4416
4417
4418
4419
4420
4421
4422
4423
4424
4425
4426
4427
if(startup->options&MAIL_OPT_USE_SUBMISSION_PORT) {
submission_socket = mail_open_socket(SOCK_STREAM,"submission");
if(submission_socket == INVALID_SOCKET) {
lprintf(LOG_ERR,"!ERROR %d opening socket", ERROR_VALUE);
cleanup(1);
return;
}
lprintf(LOG_DEBUG,"%04d SUBMISSION socket opened",submission_socket);
/*****************************/
/* Listen for incoming calls */
/*****************************/
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_addr.s_addr = htonl(startup->interface_addr);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(startup->submission_port);
if(startup->submission_port < IPPORT_RESERVED) {
if(startup->seteuid!=NULL)
startup->seteuid(FALSE);
}
result = retry_bind(submission_socket,(struct sockaddr *)&server_addr,sizeof(server_addr)
,startup->bind_retry_count,startup->bind_retry_delay,"SMTP Submission Agent",lprintf);
if(startup->submission_port < IPPORT_RESERVED) {
if(startup->seteuid!=NULL)
startup->seteuid(TRUE);
}
if(result != 0) {
lprintf(LOG_ERR,"%04d %s",submission_socket, BIND_FAILURE_HELP);
cleanup(1);
return;
}
lprintf(LOG_DEBUG,"%04d SUBMISSION socket bound to port %u"
,submission_socket, startup->submission_port);
result = listen(submission_socket, 1);
if(result != 0) {
lprintf(LOG_ERR,"%04d !ERROR %d (%d) listening on socket"
,submission_socket, result, ERROR_VALUE);
cleanup(1);
return;
}
}
if(startup->options&MAIL_OPT_ALLOW_POP3) {
/* open a socket and wait for a client */
pop3_socket = mail_open_socket(SOCK_STREAM,"pop3");
lprintf(LOG_ERR,"!ERROR %d opening POP3 socket", ERROR_VALUE);
cleanup(1);
return;
}
lprintf(LOG_DEBUG,"%04d POP3 socket opened",pop3_socket);
/*****************************/
/* Listen for incoming calls */
/*****************************/
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_addr.s_addr = htonl(startup->interface_addr);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(startup->pop3_port);
if(startup->pop3_port < IPPORT_RESERVED) {
if(startup->seteuid!=NULL)
startup->seteuid(FALSE);
}
result = retry_bind(pop3_socket,(struct sockaddr *)&server_addr,sizeof(server_addr)
,startup->bind_retry_count,startup->bind_retry_delay,"POP3 Server",lprintf);
if(startup->pop3_port < IPPORT_RESERVED) {
if(startup->seteuid!=NULL)
startup->seteuid(FALSE);
}
lprintf(LOG_ERR,"%04d %s",pop3_socket,BIND_FAILURE_HELP);
cleanup(1);
return;
}
lprintf(LOG_DEBUG,"%04d POP3 socket bound to port %u"
,pop3_socket, startup->pop3_port);
result = listen(pop3_socket, 1);
lprintf(LOG_ERR,"%04d !ERROR %d (%d) listening on POP3 socket"
,pop3_socket, result, ERROR_VALUE);
cleanup(1);
return;
sem_init(&sendmail_wakeup_sem,0,0);
if(!(startup->options&MAIL_OPT_NO_SENDMAIL))
_beginthread(sendmail_thread, 0, NULL);
lprintf(LOG_NOTICE,"%04d Mail Server thread started",server_socket);
status(STATUS_WFC);
/* Setup recycle/shutdown semaphore file lists */
shutdown_semfiles=semfile_list_init(scfg.ctrl_dir,"shutdown","mail");
recycle_semfiles=semfile_list_init(scfg.ctrl_dir,"recycle","mail");
SAFEPRINTF(path,"%smailsrvr.rec",scfg.ctrl_dir); /* legacy */
semfile_list_add(&recycle_semfiles,path);
if(!initialized) {
semfile_list_check(&initialized,recycle_semfiles);
semfile_list_check(&initialized,shutdown_semfiles);
/* signal caller that we've started up successfully */
if(startup->started!=NULL)
startup->started(startup->cbdata);
while(server_socket!=INVALID_SOCKET && !terminate_server) {
if(active_clients==0) {
if(!(startup->options&MAIL_OPT_NO_RECYCLE)) {
if((p=semfile_list_check(&initialized,recycle_semfiles))!=NULL) {
lprintf(LOG_INFO,"%04d Recycle semaphore file (%s) detected"
,server_socket,p);
break;
}
if(startup->recycle_now==TRUE) {
lprintf(LOG_NOTICE,"%04d Recycle semaphore signaled", server_socket);
startup->recycle_now=FALSE;
break;
}
if(((p=semfile_list_check(&initialized,shutdown_semfiles))!=NULL
&& lprintf(LOG_INFO,"%04d Shutdown semaphore file (%s) detected"
,server_socket,p))
|| (startup->shutdown_now==TRUE
&& lprintf(LOG_INFO,"%04d Shutdown semaphore signaled",server_socket))) {
startup->shutdown_now=FALSE;
terminate_server=TRUE;
break;
}
/* now wait for connection */
FD_ZERO(&socket_set);
FD_SET(server_socket,&socket_set);
high_socket_set=server_socket+1;
if(startup->options&MAIL_OPT_ALLOW_POP3
&& pop3_socket!=INVALID_SOCKET) {
FD_SET(pop3_socket,&socket_set);
if(pop3_socket+1>high_socket_set)
high_socket_set=pop3_socket+1;
if(startup->options&MAIL_OPT_USE_SUBMISSION_PORT
&& submission_socket!=INVALID_SOCKET) {
FD_SET(submission_socket,&socket_set);
if(submission_socket+1>high_socket_set)
high_socket_set=submission_socket+1;
}
tv.tv_sec=startup->sem_chk_freq;
tv.tv_usec=0;
if((i=select(high_socket_set,&socket_set,NULL,NULL,&tv))<1) {
continue;
if(ERROR_VALUE==EINTR)
lprintf(LOG_DEBUG,"%04d Mail Server listening interrupted",server_socket);
else if(ERROR_VALUE == ENOTSOCK)
lprintf(LOG_NOTICE,"%04d Mail Server sockets closed",server_socket);
lprintf(LOG_WARNING,"%04d !ERROR %d selecting sockets",server_socket,ERROR_VALUE);
continue;
if(server_socket!=INVALID_SOCKET && !terminate_server
&& (FD_ISSET(server_socket,&socket_set)
|| (startup->options&MAIL_OPT_USE_SUBMISSION_PORT
&& FD_ISSET(submission_socket,&socket_set)))) {
client_addr_len = sizeof(client_addr);
client_socket = accept(
FD_ISSET(server_socket,&socket_set) ? server_socket:submission_socket
,(struct sockaddr *)&client_addr
,&client_addr_len);
#if 0 /* is this necessary still? */
if(ERROR_VALUE == ENOTSOCK || ERROR_VALUE == EINVAL) {
lprintf(LOG_NOTICE,"%04d SMTP socket closed while listening"
,server_socket);
break;
}
#endif
lprintf(LOG_WARNING,"%04d SMTP !ERROR %d accepting connection"
,FD_ISSET(server_socket,&socket_set) ? server_socket:submission_socket
,ERROR_VALUE);
#ifdef _WIN32
if(WSAGetLastError()==WSAENOBUFS) /* recycle (re-init WinSock) on this error */
break;
#endif
continue;
}
if(startup->socket_open!=NULL)
startup->socket_open(startup->cbdata,TRUE);
sockets++;
if(trashcan(&scfg,inet_ntoa(client_addr.sin_addr),"ip-silent")) {
mail_close_socket(client_socket);
continue;
}
if(active_clients>=startup->max_clients) {
lprintf(LOG_WARNING,"%04d SMTP !MAXIMUM CLIENTS (%u) reached, access denied"
,client_socket, startup->max_clients);
sockprintf(client_socket,"421 Maximum active clients reached, please try again later.");
mswait(3000);
mail_close_socket(client_socket);
continue;
}
if((i=ioctlsocket(client_socket, FIONBIO, &l))!=0) {
lprintf(LOG_ERR,"%04d SMTP !ERROR %d (%d) disabling blocking on socket"
,client_socket, i, ERROR_VALUE);
mail_close_socket(client_socket);
continue;
}
if((smtp=malloc(sizeof(smtp_t)))==NULL) {
lprintf(LOG_CRIT,"%04d SMTP !ERROR allocating %u bytes of memory for smtp_t"
,client_socket, sizeof(smtp_t));
mail_close_socket(client_socket);
continue;
}
smtp->socket=client_socket;
smtp->client_addr=client_addr;
_beginthread(smtp_thread, 0, smtp);
served++;
if(pop3_socket!=INVALID_SOCKET
&& FD_ISSET(pop3_socket,&socket_set)) {
client_addr_len = sizeof(client_addr);
client_socket = accept(pop3_socket, (struct sockaddr *)&client_addr
,&client_addr_len);
#if 0 /* is this necessary still? */
if(ERROR_VALUE == ENOTSOCK || ERROR_VALUE == EINVAL) {
lprintf(LOG_NOTICE,"%04d POP3 socket closed while listening",pop3_socket);
break;
}
#endif
lprintf(LOG_WARNING,"%04d POP3 !ERROR %d accepting connection"
,pop3_socket, ERROR_VALUE);
#ifdef _WIN32
if(WSAGetLastError()==WSAENOBUFS) /* recycle (re-init WinSock) on this error */
break;
#endif
continue;
}
if(startup->socket_open!=NULL)
startup->socket_open(startup->cbdata,TRUE);
sockets++;
if(trashcan(&scfg,inet_ntoa(client_addr.sin_addr),"ip-silent")) {
mail_close_socket(client_socket);
continue;
}
if(active_clients>=startup->max_clients) {
lprintf(LOG_WARNING,"%04d POP3 !MAXIMUM CLIENTS (%u) reached, access denied"
,client_socket, startup->max_clients);
sockprintf(client_socket,"-ERR Maximum active clients reached, please try again later.");
mswait(3000);
mail_close_socket(client_socket);
continue;
}
l=1;
if((i=ioctlsocket(client_socket, FIONBIO, &l))!=0) {
lprintf(LOG_ERR,"%04d POP3 !ERROR %d (%d) disabling blocking on socket"
,client_socket, i, ERROR_VALUE);
sockprintf(client_socket,"-ERR System error, please try again later.");
mswait(3000);
mail_close_socket(client_socket);
continue;
}
if((pop3=malloc(sizeof(pop3_t)))==NULL) {
lprintf(LOG_CRIT,"%04d POP3 !ERROR allocating %u bytes of memory for pop3_t"
,client_socket,sizeof(pop3_t));
sockprintf(client_socket,"-ERR System error, please try again later.");
mswait(3000);
mail_close_socket(client_socket);
continue;
}
pop3->socket=client_socket;
pop3->client_addr=client_addr;
_beginthread(pop3_thread, 0, pop3);
served++;
if(active_clients) {
lprintf(LOG_DEBUG,"%04d Waiting for %d active clients to disconnect..."
,server_socket, active_clients);
start=time(NULL);
while(active_clients) {
if(time(NULL)-start>startup->max_inactivity) {
lprintf(LOG_WARNING,"%04d !TIMEOUT waiting for %d active clients"
,server_socket, active_clients);
break;
}
mswait(100);
if(sendmail_running) {
terminate_sendmail=TRUE;
sem_post(&sendmail_wakeup_sem);
mswait(100);
}
if(sendmail_running) {
lprintf(LOG_DEBUG,"%04d Waiting for SendMail thread to terminate..."
,server_socket);
start=time(NULL);
while(sendmail_running) {
if(time(NULL)-start>TIMEOUT_THREAD_WAIT) {
lprintf(LOG_WARNING,"%04d !TIMEOUT waiting for sendmail thread to terminate"
,server_socket);
break;
}
mswait(500);
if(!sendmail_running) {
while(sem_destroy(&sendmail_wakeup_sem)==-1 && errno==EBUSY) {
mswait(1);
cleanup(0);
if(!terminate_server) {
lprintf(LOG_INFO,"Recycling server...");
if(startup->recycle!=NULL)
startup->recycle(startup->cbdata);
} while(!terminate_server);