|
|
1.1 root 1: /* getnode.cpp */
2:
3: /* Synchronet node information retrieval functions */
4:
1.1.1.2 ! root 5: /* $Id: getnode.cpp,v 1.28 2003/05/22 02:12:45 rswindell Exp $ */
1.1 root 6:
7: /****************************************************************************
8: * @format.tab-size 4 (Plain Text/Source Code File Header) *
9: * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) *
10: * *
1.1.1.2 ! root 11: * Copyright 2003 Rob Swindell - http://www.synchro.net/copyright.html *
1.1 root 12: * *
13: * This program is free software; you can redistribute it and/or *
14: * modify it under the terms of the GNU General Public License *
15: * as published by the Free Software Foundation; either version 2 *
16: * of the License, or (at your option) any later version. *
17: * See the GNU General Public License for more details: gpl.txt or *
18: * http://www.fsf.org/copyleft/gpl.html *
19: * *
20: * Anonymous FTP access to the most recent released source is available at *
21: * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net *
22: * *
23: * Anonymous CVS access to the development source and modification history *
24: * is available at cvs.synchro.net:/cvsroot/sbbs, example: *
25: * cvs -d :pserver:[email protected]:/cvsroot/sbbs login *
26: * (just hit return, no password is necessary) *
27: * cvs -d :pserver:[email protected]:/cvsroot/sbbs checkout src *
28: * *
29: * For Synchronet coding style and modification guidelines, see *
30: * http://www.synchro.net/source.html *
31: * *
32: * You are encouraged to submit any modifications (preferably in Unix diff *
33: * format) via e-mail to [email protected] *
34: * *
35: * Note: If this box doesn't appear square, then you need to fix your tabs. *
36: ****************************************************************************/
37:
38: #include "sbbs.h"
39: #include "cmdshell.h"
40:
41: /****************************************************************************/
42: /* Reads the data for node number 'number' into the structure 'node' */
43: /* from NODE.DAB */
44: /* if lockit is non-zero, locks this node's record. putnodedat() unlocks it */
45: /****************************************************************************/
1.1.1.2 ! root 46: int sbbs_t::getnodedat(uint number, node_t *node, bool lockit)
1.1 root 47: {
1.1.1.2 ! root 48: char str[MAX_PATH+1];
! 49: int rd=sizeof(node_t);
! 50: int count;
1.1 root 51:
1.1.1.2 ! root 52: if(node==NULL || number<1)
! 53: return(-1);
! 54:
! 55: if(number>cfg.sys_nodes) {
1.1 root 56: errormsg(WHERE,ERR_CHK,"node number",number);
1.1.1.2 ! root 57: return(-1);
! 58: }
1.1 root 59:
1.1.1.2 ! root 60: if(node!=&thisnode)
! 61: memset(node,0,sizeof(node_t));
! 62: sprintf(str,"%snode.dab",cfg.ctrl_dir);
1.1 root 63: if(nodefile==-1) {
64: if((nodefile=nopen(str,O_RDWR|O_DENYNONE))==-1) {
65: errormsg(WHERE,ERR_OPEN,str,O_RDWR|O_DENYNONE);
1.1.1.2 ! root 66: return(errno);
1.1 root 67: }
68: }
1.1.1.2 ! root 69: else
! 70: utime(str,NULL); /* NFS fix... utime() forces a cache refresh */
1.1 root 71:
72: number--; /* make zero based */
1.1.1.2 ! root 73: for(count=0;count<LOOP_NODEDAB;count++) {
! 74: if(count)
1.1 root 75: mswait(100);
1.1.1.2 ! root 76: if(lockit && lock(nodefile,(long)number*sizeof(node_t),sizeof(node_t))!=0) {
! 77: unlock(nodefile,(long)number*sizeof(node_t),sizeof(node_t));
! 78: continue;
! 79: }
1.1 root 80: lseek(nodefile,(long)number*sizeof(node_t),SEEK_SET);
1.1.1.2 ! root 81: rd=read(nodefile,node,sizeof(node_t));
! 82: if(!lockit || rd!=sizeof(node_t))
! 83: unlock(nodefile,(long)number*sizeof(node_t),sizeof(node_t));
! 84: if(rd==sizeof(node_t))
1.1 root 85: break;
86: }
1.1.1.2 ! root 87: if(!lockit && cfg.node_misc&NM_CLOSENODEDAB) {
1.1 root 88: close(nodefile);
89: nodefile=-1;
90: }
91:
92: if(count==LOOP_NODEDAB) {
1.1.1.2 ! root 93: errormsg(WHERE,rd==sizeof(node_t) ? ERR_LOCK : ERR_READ,"node.dab",number+1);
! 94: return(-2);
! 95: }
! 96: if(count>(LOOP_NODEDAB/2)) {
! 97: sprintf(str,"NODE.DAB (node %d) COLLISION - Count: %d"
! 98: ,number+1, count);
! 99: logline("!!",str);
1.1 root 100: }
1.1.1.2 ! root 101:
! 102: return(0);
1.1 root 103: }
104:
105: /****************************************************************************/
106: /* Synchronizes all the nodes knowledge of the other nodes' actions, mode, */
107: /* status and other flags. */
108: /* Assumes that getnodedat(node_num,&thisnode,0) was called right before it */
109: /* is called. */
110: /****************************************************************************/
111: void sbbs_t::nodesync()
112: {
113: char str[256],today[32];
1.1.1.2 ! root 114: int atr=curatr;
1.1 root 115:
1.1.1.2 ! root 116: if(nodesync_inside || !online)
! 117: return;
1.1 root 118: nodesync_inside=1;
119:
120: if(thisnode.action!=action) {
1.1.1.2 ! root 121: if(getnodedat(cfg.node_num,&thisnode,true)==0) {
! 122: thisnode.action=action;
! 123: putnodedat(cfg.node_num,&thisnode);
! 124: }
! 125: }
1.1 root 126:
127: criterrs=thisnode.errors;
128:
129: if(sys_status&SS_USERON) {
1.1.1.2 ! root 130:
! 131: if(thisnode.status==NODE_WFC) {
! 132: errorlog("NODE STATUS FIXUP");
! 133: if(getnodedat(cfg.node_num,&thisnode,true)==0) {
! 134: thisnode.status=NODE_INUSE;
! 135: putnodedat(cfg.node_num,&thisnode);
! 136: }
! 137: }
! 138:
1.1 root 139: if(!(sys_status&SS_NEWDAY)) {
140: now=time(NULL);
141: unixtodstr(&cfg,logontime,str);
142: unixtodstr(&cfg,now,today);
143: if(strcmp(str,today)) { /* New day, clear "today" user vars */
144: sys_status|=SS_NEWDAY; // So we don't keep doing this over&over
1.1.1.2 ! root 145: resetdailyuserdat(&cfg,&useron);
! 146: }
! 147: }
1.1 root 148: if(thisnode.misc&NODE_UDAT && !(useron.rest&FLAG('G'))) { /* not guest */
149: getuserdat(&cfg, &useron);
1.1.1.2 ! root 150: if(getnodedat(cfg.node_num,&thisnode,true)==0) {
! 151: thisnode.misc&=~NODE_UDAT;
! 152: putnodedat(cfg.node_num,&thisnode);
! 153: }
! 154: }
1.1 root 155: if(thisnode.misc&NODE_MSGW)
156: getsmsg(useron.number); /* getsmsg clears MSGW flag */
157: if(thisnode.misc&NODE_NMSG)
1.1.1.2 ! root 158: getnmsg(); /* getnmsg clears NMSG flag */
! 159: }
1.1 root 160:
161: if(cfg.sync_mod[0])
162: exec_bin(cfg.sync_mod,&main_csi);
163:
164: if(thisnode.misc&NODE_INTR) {
165: bputs(text[NodeLocked]);
166: logline(nulstr,"Interrupted");
167: hangup();
168: nodesync_inside=0;
1.1.1.2 ! root 169: return;
! 170: }
1.1 root 171:
172: if(thisnode.misc&NODE_LCHAT) { // pulled into local chat with sysop
173: SAVELINE;
174: privchat(true);
175: RESTORELINE;
176: }
177:
178: if(sys_status&SS_USERON && memcmp(&nodesync_user,&useron,sizeof(user_t))) {
179: getusrdirs();
180: getusrsubs();
1.1.1.2 ! root 181: memcpy(&nodesync_user,&useron,sizeof(nodesync_user));
! 182: }
1.1 root 183:
184: if(sys_status&SS_USERON && online && (timeleft/60)<(5-timeleft_warn)
185: && !SYSOP) {
186: timeleft_warn=5-(timeleft/60);
187: attr(LIGHTGRAY);
188: bprintf(text[OnlyXminutesLeft]
1.1.1.2 ! root 189: ,((ushort)timeleft/60)+1,(timeleft/60) ? "s" : nulstr);
! 190: }
1.1 root 191:
192: attr(atr); /* replace original attributes */
193: nodesync_inside=0;
194: }
195:
196: /****************************************************************************/
197: /* Prints short messages waiting for this node, if any... */
198: /****************************************************************************/
1.1.1.2 ! root 199: int sbbs_t::getnmsg()
1.1 root 200: {
1.1.1.2 ! root 201: char str[MAX_PATH+1], HUGE16 *buf;
! 202: int file;
! 203: long length;
! 204:
! 205: if(getnodedat(cfg.node_num,&thisnode,true)==0) {
! 206: thisnode.misc&=~NODE_NMSG; /* clear the NMSG flag */
! 207: putnodedat(cfg.node_num,&thisnode);
! 208: }
1.1 root 209:
210: sprintf(str,"%smsgs/n%3.3u.msg",cfg.data_dir,cfg.node_num);
211: if(flength(str)<1L)
1.1.1.2 ! root 212: return(0);
1.1 root 213: if((file=nopen(str,O_RDWR))==-1) {
214: /**
215: errormsg(WHERE,ERR_OPEN,str,O_RDWR);
216: **/
1.1.1.2 ! root 217: return(errno);
! 218: }
1.1 root 219: length=filelength(file);
220: if(!length) {
221: close(file);
1.1.1.2 ! root 222: return(0);
! 223: }
1.1 root 224: if((buf=(char *)LMALLOC(length+1))==NULL) {
225: close(file);
226: errormsg(WHERE,ERR_ALLOC,str,length+1);
1.1.1.2 ! root 227: return(-1);
! 228: }
1.1 root 229: if(lread(file,buf,length)!=length) {
230: close(file);
231: FREE(buf);
232: errormsg(WHERE,ERR_READ,str,length);
1.1.1.2 ! root 233: return(errno);
! 234: }
1.1 root 235: chsize(file,0L);
236: close(file);
237: buf[length]=0;
238:
239: if(thisnode.action==NODE_MAIN || thisnode.action==NODE_XFER
240: || sys_status&SS_IN_CTRLP) {
1.1.1.2 ! root 241: CRLF;
! 242: }
1.1 root 243: putmsg(buf,P_NOATCODES);
244: LFREE(buf);
1.1.1.2 ! root 245:
! 246: return(0);
1.1 root 247: }
248:
249: /****************************************************************************/
250: /* 'ext' must be at least 128 bytes! */
251: /****************************************************************************/
1.1.1.2 ! root 252: int sbbs_t::getnodeext(uint number, char *ext)
1.1 root 253: {
1.1.1.2 ! root 254: char str[MAX_PATH+1];
! 255: int rd,count;
1.1 root 256:
257: if(!number || number>cfg.sys_nodes) {
258: errormsg(WHERE,ERR_CHK,"node number",number);
1.1.1.2 ! root 259: return(-1);
! 260: }
1.1 root 261:
262: sprintf(str,"%snode.exb",cfg.ctrl_dir);
263: if((node_ext=nopen(str,O_RDONLY|O_DENYNONE))==-1) {
264: memset(ext,0,128);
265: errormsg(WHERE,ERR_OPEN,str,O_RDONLY|O_DENYNONE);
1.1.1.2 ! root 266: return(errno);
1.1 root 267: }
268:
269: number--; /* make zero based */
1.1.1.2 ! root 270: for(count=0;count<LOOP_NODEDAB;count++) {
! 271: if(count)
1.1 root 272: mswait(100);
1.1.1.2 ! root 273: if(lock(node_ext,(long)number*128L,128)!=0)
! 274: continue;
1.1 root 275: lseek(node_ext,(long)number*128L,SEEK_SET);
1.1.1.2 ! root 276: rd=read(node_ext,ext,128);
! 277: unlock(node_ext,(long)number*128L,128);
! 278: if(rd==128)
1.1 root 279: break;
1.1.1.2 ! root 280: }
1.1 root 281: close(node_ext);
282: node_ext=-1;
283:
1.1.1.2 ! root 284:
1.1 root 285: if(count==LOOP_NODEDAB) {
286: errormsg(WHERE,ERR_READ,"node.exb",number+1);
1.1.1.2 ! root 287: return(-2);
! 288: }
! 289: if(count>(LOOP_NODEDAB/2)) {
! 290: sprintf(str,"NODE.EXB (node %d) COLLISION - Count: %d"
! 291: ,number+1, count);
! 292: logline("!!",str);
1.1 root 293: }
1.1.1.2 ! root 294:
! 295: return(0);
1.1 root 296: }
297:
298:
299: /****************************************************************************/
300: /* Prints short messages waiting for 'usernumber', if any... */
301: /* then deletes them. */
302: /****************************************************************************/
1.1.1.2 ! root 303: int sbbs_t::getsmsg(int usernumber)
1.1 root 304: {
1.1.1.2 ! root 305: char str[MAX_PATH+1], HUGE16 *buf;
! 306: int file;
! 307: long length;
1.1 root 308:
309: sprintf(str,"%smsgs/%4.4u.msg",cfg.data_dir,usernumber);
310: if(flength(str)<1L)
1.1.1.2 ! root 311: return(0);
1.1 root 312: if((file=nopen(str,O_RDWR))==-1) {
313: errormsg(WHERE,ERR_OPEN,str,O_RDWR);
1.1.1.2 ! root 314: return(errno);
! 315: }
1.1 root 316: length=filelength(file);
317: if((buf=(char *)LMALLOC(length+1))==NULL) {
318: close(file);
319: errormsg(WHERE,ERR_ALLOC,str,length+1);
1.1.1.2 ! root 320: return(-1);
! 321: }
1.1 root 322: if(lread(file,buf,length)!=length) {
323: close(file);
324: FREE(buf);
325: errormsg(WHERE,ERR_READ,str,length);
1.1.1.2 ! root 326: return(errno);
! 327: }
1.1 root 328: chsize(file,0L);
329: close(file);
330: buf[length]=0;
331: getnodedat(cfg.node_num,&thisnode,0);
332: if(thisnode.action==NODE_MAIN || thisnode.action==NODE_XFER
333: || sys_status&SS_IN_CTRLP) {
1.1.1.2 ! root 334: CRLF;
! 335: }
1.1 root 336: if(thisnode.misc&NODE_MSGW) {
1.1.1.2 ! root 337: if(getnodedat(cfg.node_num,&thisnode,true)==0) {
! 338: thisnode.misc&=~NODE_MSGW;
! 339: putnodedat(cfg.node_num,&thisnode);
! 340: }
! 341: }
1.1 root 342: putmsg(buf,P_NOATCODES);
343: LFREE(buf);
1.1.1.2 ! root 344:
! 345: return(0);
1.1 root 346: }
347:
348: /****************************************************************************/
349: /* This function lists users that are online. */
350: /* If listself is true, it will list the current node. */
351: /* Returns number of active nodes (not including current node). */
352: /****************************************************************************/
353: int sbbs_t::whos_online(bool listself)
354: {
1.1.1.2 ! root 355: int i,j;
! 356: node_t node;
1.1 root 357:
358: CRLF;
359: bputs(text[NodeLstHdr]);
360: for(j=0,i=1;i<=cfg.sys_nodes && i<=cfg.sys_lastnode;i++) {
361: getnodedat(i,&node,0);
362: if(i==cfg.node_num) {
363: if(listself)
364: printnodedat(i,&node);
1.1.1.2 ! root 365: continue;
! 366: }
1.1 root 367: if(node.status==NODE_INUSE || (SYSOP && node.status==NODE_QUIET)) {
368: printnodedat(i,&node);
369: if(!lastnodemsg)
370: lastnodemsg=i;
1.1.1.2 ! root 371: j++;
! 372: }
! 373: }
1.1 root 374: if(!j)
375: bputs(text[NoOtherActiveNodes]);
376: return(j);
377: }
378:
1.1.1.2 ! root 379: void sbbs_t::nodelist(void)
! 380: {
! 381: node_t node;
! 382:
! 383: CRLF;
! 384: bputs(text[NodeLstHdr]);
! 385: for(int i=1;i<=cfg.sys_nodes && i<=cfg.sys_lastnode;i++) {
! 386: getnodedat(i,&node,0);
! 387: printnodedat(i,&node);
! 388: }
! 389: }
! 390:
1.1 root 391: /****************************************************************************/
392: /* Displays the information for node number 'number' contained in 'node' */
393: /****************************************************************************/
394: void sbbs_t::printnodedat(uint number, node_t* node)
395: {
396: uint i;
397: char hour,mer[3];
398: char tmp[512];
399:
400: attr(cfg.color[clr_nodenum]);
401: bprintf("%3d ",number);
402: attr(cfg.color[clr_nodestatus]);
403: switch(node->status) {
404: case NODE_WFC:
405: bputs("Waiting for call");
406: break;
407: case NODE_OFFLINE:
408: bputs("Offline");
409: break;
410: case NODE_NETTING:
411: bputs("Networking");
412: break;
413: case NODE_LOGON:
414: bputs("At logon prompt");
415: break;
416: case NODE_EVENT_WAITING:
417: bputs("Waiting for all nodes to become inactive");
418: break;
419: case NODE_EVENT_LIMBO:
420: bprintf("Waiting for node %d to finish external event",node->aux);
421: break;
422: case NODE_EVENT_RUNNING:
423: bputs("Running external event");
424: break;
425: case NODE_NEWUSER:
426: attr(cfg.color[clr_nodeuser]);
427: bputs("New user");
428: attr(cfg.color[clr_nodestatus]);
429: bputs(" applying for access ");
430: if(!node->connection)
431: bputs("Locally");
432: else if(node->connection==0xffff)
433: bprintf("via telnet");
434: else
435: bprintf("at %ubps",node->connection);
436: break;
437: case NODE_QUIET:
438: if(!SYSOP) {
439: bputs("Waiting for call");
1.1.1.2 ! root 440: break;
! 441: }
1.1 root 442: case NODE_INUSE:
443: if(node->misc&NODE_EXT) {
444: getnodeext(number,tmp);
445: bputs(tmp);
1.1.1.2 ! root 446: break;
! 447: }
1.1 root 448: attr(cfg.color[clr_nodeuser]);
449: if(node->misc&NODE_ANON && !SYSOP)
450: bputs("UNKNOWN USER");
451: else
452: bputs(username(&cfg,node->useron,tmp));
453: attr(cfg.color[clr_nodestatus]);
454: bputs(" ");
455: switch(node->action) {
456: case NODE_MAIN:
457: bputs("at main menu");
458: break;
459: case NODE_RMSG:
460: bputs("reading messages");
461: break;
462: case NODE_RMAL:
463: bputs("reading mail");
464: break;
465: case NODE_RSML:
466: bputs("reading sent mail");
467: break;
468: case NODE_RTXT:
469: bputs("reading text files");
470: break;
471: case NODE_PMSG:
472: bputs("posting message");
473: break;
474: case NODE_SMAL:
475: bputs("sending mail");
476: break;
477: case NODE_AMSG:
478: bputs("posting auto-message");
479: break;
480: case NODE_XTRN:
481: if(node->aux<1 || node->aux>cfg.total_xtrns)
482: bputs("at external program menu");
483: else {
484: bputs("running ");
485: i=node->aux-1;
486: if(SYSOP || chk_ar(cfg.xtrn[i]->ar,&useron))
487: bputs(cfg.xtrn[node->aux-1]->name);
488: else
1.1.1.2 ! root 489: bputs("external program");
! 490: }
1.1 root 491: break;
492: case NODE_DFLT:
493: bputs("changing defaults");
494: break;
495: case NODE_XFER:
496: bputs("at transfer menu");
497: break;
498: case NODE_RFSD:
499: bprintf("retrieving from device #%d",node->aux);
500: break;
501: case NODE_DLNG:
502: bprintf("downloading");
503: break;
504: case NODE_ULNG:
505: bputs("uploading");
506: break;
507: case NODE_BXFR:
508: bputs("transferring bidirectional");
509: break;
510: case NODE_LFIL:
511: bputs("listing files");
512: break;
513: case NODE_LOGN:
514: bputs("logging on");
515: break;
516: case NODE_LCHT:
517: bprintf("in local chat with %s",cfg.sys_op);
518: break;
519: case NODE_MCHT:
520: if(node->aux) {
521: bprintf("in multinode chat channel %d",node->aux&0xff);
522: if(node->aux&0x1f00) { /* password */
523: outchar('*');
524: if(SYSOP)
1.1.1.2 ! root 525: bprintf(" %s",unpackchatpass(tmp,node));
! 526: }
! 527: }
1.1 root 528: else
529: bputs("in multinode global chat channel");
530: break;
531: case NODE_PAGE:
532: bprintf("paging node %u for private chat",node->aux);
533: break;
534: case NODE_PCHT:
535: if(node->aux)
536: bprintf("in private chat with node %u",node->aux);
537: else
538: bprintf("in local chat with %s",cfg.sys_op);
539: break;
540: case NODE_GCHT:
541: i=node->aux;
542: if(i>=cfg.total_gurus)
543: i=0;
544: bprintf("chatting with %s",cfg.guru[i]->name);
545: break;
546: case NODE_CHAT:
547: bputs("in chat section");
548: break;
549: case NODE_TQWK:
550: bputs("transferring QWK packet");
551: break;
552: case NODE_SYSP:
553: bputs("performing sysop activities");
554: break;
555: default:
556: bputs(ultoa(node->action,tmp,10));
557: break; }
558: if(!node->connection)
559: bputs(" locally");
560: else if(node->connection==0xffff)
561: bprintf(" via telnet");
562: else
563: bprintf(" at %ubps",node->connection);
564: if(node->action==NODE_DLNG) {
565: if(cfg.sys_misc&SM_MILITARY) {
566: hour=node->aux/60;
1.1.1.2 ! root 567: mer[0]=0;
! 568: }
1.1 root 569: else if((node->aux/60)>=12) {
570: if(node->aux/60==12)
571: hour=12;
572: else
573: hour=(node->aux/60)-12;
1.1.1.2 ! root 574: strcpy(mer,"pm");
! 575: }
1.1 root 576: else {
577: if((node->aux/60)==0) /* 12 midnite */
578: hour=12;
579: else hour=node->aux/60;
1.1.1.2 ! root 580: strcpy(mer,"am");
! 581: }
1.1 root 582: bprintf(" ETA %02d:%02d %s"
1.1.1.2 ! root 583: ,hour,node->aux%60,mer);
! 584: }
! 585: break;
! 586: }
1.1 root 587: i=NODE_LOCK;
588: if(node->status==NODE_INUSE || SYSOP)
589: i|=NODE_POFF|NODE_AOFF|NODE_MSGW|NODE_NMSG;
590: if(node->misc&i) {
591: bputs(" (");
592: if(node->misc&(i&NODE_AOFF))
593: outchar('A');
594: if(node->misc&NODE_LOCK)
595: outchar('L');
596: if(node->misc&(i&(NODE_MSGW|NODE_NMSG)))
597: outchar('M');
598: if(node->misc&(i&NODE_POFF))
599: outchar('P');
1.1.1.2 ! root 600: outchar(')');
! 601: }
1.1 root 602: if(SYSOP && ((node->misc
603: &(NODE_ANON|NODE_UDAT|NODE_INTR|NODE_RRUN|NODE_EVENT|NODE_DOWN|NODE_LCHAT))
604: || node->status==NODE_QUIET)) {
605: bputs(" [");
606: if(node->misc&NODE_ANON)
607: outchar('A');
608: if(node->misc&NODE_INTR)
609: outchar('I');
610: if(node->misc&NODE_RRUN)
611: outchar('R');
612: if(node->misc&NODE_UDAT)
613: outchar('U');
614: if(node->status==NODE_QUIET)
615: outchar('Q');
616: if(node->misc&NODE_EVENT)
617: outchar('E');
618: if(node->misc&NODE_DOWN)
619: outchar('D');
620: if(node->misc&NODE_LCHAT)
621: outchar('C');
1.1.1.2 ! root 622: outchar(']');
! 623: }
1.1 root 624: if(node->errors && SYSOP) {
625: attr(cfg.color[clr_err]);
1.1.1.2 ! root 626: bprintf(" %d error%c",node->errors, node->errors>1 ? 's' : '\0' );
! 627: }
1.1 root 628: attr(LIGHTGRAY);
629: CRLF;
630: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.