1 /*
2 * ufdbanalyse.c - URLfilterDB
3 *
4 * ufdbGuard is copyrighted (C) 2005-2020 by URLfilterDB with all rights reserved.
5 *
6 * Parts of the ufdbGuard daemon are based on squidGuard.
7 * This module is NOT based on squidGuard.
8 *
9 * read a Squid log file and produce a report with a table showing
10 * percentages for categories.
11 *
12 * This program is meant to be used as a tool to find out what types
13 * of websites are visited.
14 *
15 * RCS: $Id: ufdbAnalyse.c,v 1.36 2020/08/20 14:50:58 root Exp root $
16 */
17
18 /* we need maximum speed: */
19 #undef _FORTIFY_SOURCE
20
21 #define UFDB_API_NO_THREADS 1
22
23 #include "ufdb.h"
24 #include "ufdblib.h"
25
26 #include <sys/types.h>
27 #include <sys/socket.h>
28 #include <sys/stat.h>
29 #include <sys/time.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <strings.h>
33 #include <unistd.h>
34 #include <errno.h>
35 #include <fcntl.h>
36
37 #ifdef __cplusplus
38 extern "C" {
39 #endif
40
41
42 #if HAVE_PUTC_UNLOCKED
43
44 #define myfast_putc(c,fp) putc_unlocked(c,fp)
45 #define myfast_getc(fp) getc_unlocked(fp)
46
fast_puts(const char * s,FILE * fp)47 __inline__ static int fast_puts( const char * s, FILE * fp )
48 {
49 int retval;
50
51 retval = 1;
52 while (*s != '\0' && ((retval = myfast_putc(*s,fp)) != EOF))
53 s++;
54
55 return retval;
56 }
57
58
fast_gets(char * s,int size,FILE * fp)59 __inline__ static char * fast_gets( char * s, int size, FILE * fp )
60 {
61 char * retval;
62 int n;
63 int ch;
64
65 retval = s;
66 n = 0;
67 while (n < size &&
68 (ch = myfast_getc(fp)) != EOF)
69 {
70 n++;
71 *s++ = ch;
72 if (ch == '\n')
73 {
74 *s = '\0';
75 return retval;
76 }
77 }
78
79 if (n == 0 && ch == EOF)
80 return NULL;
81
82 *s = '\0';
83 return retval;
84 }
85
86 #else
87
88 #define myfast_putc(c,fp) fputc(c,fp)
89 #define fast_puts(s,fp) fputs(s,fp)
90
91 #define myfast_getc(fp) fgetc(fp)
92 #define fast_gets(s,size,fp) fgets(s,size,fp)
93
94 #endif
95
96 #if 0
97 void ufdbFree( void * ptr )
98 {
99 if (ptr != NULL)
100 free( ptr );
101 }
102
103 void * ufdbMalloc( size_t n )
104 {
105 char * mem;
106
107 mem = malloc( n );
108 return mem;
109 }
110
111 void * ufdbRealloc( void * ptr, size_t n )
112 {
113 char * mem;
114
115 mem = realloc( ptr, n );
116 return mem;
117 }
118
119 void * ufdbCalloc( size_t n, size_t num )
120 {
121 char * mem;
122
123 mem = calloc( n, num );
124 return mem;
125 }
126
127 void ufdbGetMallocMutex( char * fn )
128 {
129 }
130
131 void ufdbReleaseMallocMutex( char * fn )
132 {
133 }
134
135 char * ufdbStrdup( const char * s )
136 {
137 int size = strlen( s ) + 1;
138 return strcpy( ufdbMalloc(size), s );
139 }
140 #endif
141
142
143
ufdbLogMessage(const char * line,...)144 void ufdbLogMessage( const char * line, ... )
145 {
146 fprintf( stderr, "%s\n:", line );
147 }
148
149
ufdbLogError(const char * line,...)150 void ufdbLogError( const char * line, ... )
151 {
152 fprintf( stderr, "%s\n:", line );
153 }
154
155
ufdbLogFatalError(const char * line,...)156 void ufdbLogFatalError( const char * line, ... )
157 {
158 fprintf( stderr, "%s\n:", line );
159 }
160
161
ufdbSetGlobalErrorLogFile(char * logdir,char * basename,int mutex_is_used)162 void ufdbSetGlobalErrorLogFile(
163 char * logdir __attribute__((unused)),
164 char * basename __attribute__((unused)),
165 int mutex_is_used __attribute__((unused)) )
166 {
167 }
168
169
170 #if 0
171 int pthread_mutex_lock( void * mutex )
172 {
173 return 0;
174 }
175
176 int pthread_mutex_unlock( void * mutex )
177 {
178 return 0;
179 }
180 #endif
181
182
usage(void)183 static void usage( void )
184 {
185 fprintf( stderr, "usage: ufdbAnalyse [-a] -l <squid-log-file> -d <domainname> -e <email-address> -n <full-name>\n" );
186 fprintf( stderr, "flags: -a logfile has format of Apache instead of Squid\n" );
187 fprintf( stderr, " -l Squid log file (may be used multiple times)\n" );
188 fprintf( stderr, " -d your domainname, e.g. example.com\n" );
189 fprintf( stderr, " -e your email address, e.g. joe@example.com\n" );
190 fprintf( stderr, " -n your full name\n" );
191 fprintf( stderr, " -v verbose output\n" );
192 exit( 1 );
193 }
194
195
parseApacheLog(char * line,char ** code,char ** nbytes,char ** url)196 static int parseApacheLog( char * line, char ** code, char ** nbytes, char ** url )
197 {
198 char * p;
199 char * slash;
200 char * u;
201
202 /* a line in a Apache log file looks like this:
203 * 203.199.204.12 - - [30/Nov/2007:11:27:21 +0100] "GET /smallcross.gif HTTP/1.0" 200 61
204 * "http://74.86.20.90/livescore/data/crbz-31739122ndtv06/2007/2007_IND_PAK/IND_PAK_NOV30_DEC04/gen_scag.html"
205 * "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; FDM; InfoPath.1; .NET CLR 2.0.50727)"
206 */
207
208 p = strchr( line, '"' );
209 if (p == NULL)
210 return 0;
211
212 p = strtok( p, " \t" ); /* HTTP command */
213 if (p == NULL)
214 return 0;
215
216 u = strtok( NULL, " \t" ); /* URL */
217 if (u == NULL)
218 return 0;
219
220 /* strip the URL for privacy; remove username/password and query terms */
221
222 p = strstr( u, "://" );
223 if (p != NULL)
224 u = p + 3;
225 slash = strchr( u, '/' );
226 p = strchr( u, '@' );
227 if (p != NULL)
228 {
229 if (slash == NULL || p < slash)
230 u = p + 1;
231 }
232
233 if (slash != NULL)
234 {
235 /* truncate very long URLs */
236 if (strlen(slash) > 120)
237 *(slash+120) = '\0';
238
239 p = strchr( slash, '?' );
240 if (p != NULL)
241 *p = '\0';
242 p = strchr( slash, '&' );
243 if (p != NULL)
244 *p = '\0';
245 p = strchr( slash, ';' );
246 if (p != NULL)
247 *p = '\0';
248 p = strchr( slash, '%' );
249 if (p != NULL)
250 {
251 char * p2;
252 p2 = strstr( p, "%3F" );
253 if (p2 != NULL)
254 *p2 = '\0';
255 p2 = strstr( p, "%3f" );
256 if (p2 != NULL)
257 *p2 = '\0';
258 p2 = strstr( p, "%26" );
259 if (p2 != NULL)
260 *p2 = '\0';
261 p2 = strstr( p, "%3B" );
262 if (p2 != NULL)
263 *p2 = '\0';
264 p2 = strstr( p, "%3b" );
265 if (p2 != NULL)
266 *p2 = '\0';
267 }
268 }
269 *url = u;
270
271 p = strtok( NULL, " \t" ); /* protocol */
272 if (p == NULL)
273 return 0;
274
275 *code = strtok( NULL, " \t" ); /* HTTP return code */
276 if (*code == NULL)
277 return 0;
278
279 *nbytes = strtok( NULL, " \t" ); /* #bytes */
280 if (*nbytes == NULL)
281 return 0;
282
283 /* skip the rest of the line */
284
285 return 1;
286 }
287
288
parseSquidLog(char * line,char ** code,char ** nbytes,char ** url)289 static int parseSquidLog( char * line, char ** code, char ** nbytes, char ** url )
290 {
291 char * p;
292 char * slash;
293 char * u;
294
295 /* a line in a Squid log file looks like this:
296 * 1195621581.452 74 10.1.1.2 TCP_MISS/200 1169 GET http://example.com - DIRECT/194.46.8.130 text/html
297 */
298 if (strtok( line, " \t" ) == NULL) /* time */
299 return 0;
300
301 if (strtok( NULL, " \t" ) == NULL) /* dummy */
302 return 0;
303
304 if (strtok( NULL, " \t" ) == NULL) /* IP */
305 return 0;
306
307 *code = strtok( NULL, " \t" ); /* Squid code / HTTP code */
308 if (*code == NULL)
309 return 0;
310 p = strchr( *code, '/' );
311 if (p != NULL)
312 *code = p + 1;
313
314 *nbytes = strtok( NULL, " \t" ); /* #bytes */
315 if (*nbytes == NULL)
316 return 0;
317
318 if (strtok( NULL, " \t" ) == NULL) /* HTTP command */
319 return 0;
320
321 u = strtok( NULL, " \t" ); /* URL */
322 if (u == NULL)
323 return 0;
324
325 /* ignore the rest of the input line */
326
327 p = strstr( u, "://" );
328 if (p != NULL)
329 u = p + 3;
330 slash = strchr( u, '/' );
331 p = strchr( u, '@' );
332 if (p != NULL)
333 {
334 if (slash == NULL || p < slash)
335 u = p + 1;
336 }
337
338 /* strip the URL for privacy; remove username/password and query terms */
339 if (slash != NULL)
340 {
341 /* truncate very long URLs */
342 if (strlen(slash) > 120)
343 *(slash+120) = '\0';
344
345 p = strchr( slash, '?' );
346 if (p != NULL)
347 *p = '\0';
348 p = strchr( slash, '&' );
349 if (p != NULL)
350 *p = '\0';
351 p = strchr( slash, ';' );
352 if (p != NULL)
353 *p = '\0';
354 p = strchr( slash, '%' );
355 if (p != NULL)
356 {
357 char * p2;
358 p2 = strstr( p, "%3F" );
359 if (p2 != NULL)
360 *p2 = '\0';
361 p2 = strstr( p, "%3f" );
362 if (p2 != NULL)
363 *p2 = '\0';
364 p2 = strstr( p, "%26" );
365 if (p2 != NULL)
366 *p2 = '\0';
367 p2 = strstr( p, "%3B" );
368 if (p2 != NULL)
369 *p2 = '\0';
370 p2 = strstr( p, "%3b" );
371 if (p2 != NULL)
372 *p2 = '\0';
373 }
374 }
375
376 *url = u;
377
378 return 1;
379 }
380
381
parseLogfile(FILE * fp,char * logfilename,int apachelog,int verbose,unsigned long * total)382 static int parseLogfile(
383 FILE * fp,
384 char * logfilename,
385 int apachelog,
386 int verbose,
387 unsigned long * total )
388 {
389 int errors;
390 int n;
391 unsigned long nbytes_read;
392 unsigned long nbytes_sent;
393 unsigned long nlines_sent;
394 char * code;
395 char * nbytes;
396 char * url;
397 FILE * logfile;
398 char linebuf[16*1024];
399 char output[4192];
400
401 logfile = fopen( logfilename, "r" );
402 if (logfile == NULL)
403 {
404 fprintf( stderr, "cannot open \"%s\"\n", logfilename );
405 return 0;
406 }
407
408 if (verbose)
409 printf( "processing log \"%s\"\n", logfilename );
410
411 errors = 0;
412 nbytes = 0;
413 nbytes_read = 0;
414 nbytes_sent = 0;
415 nlines_sent = 0;
416
417 code = NULL;
418 url = NULL;
419 while (fast_gets( linebuf, 16*1024-2, logfile ) != NULL)
420 {
421 linebuf[16*1024-2] = '\0';
422 nbytes_read += strlen( linebuf );
423 nlines_sent++;
424
425 if (apachelog)
426 n = parseApacheLog( linebuf, &code, &nbytes, &url );
427 else
428 n = parseSquidLog( linebuf, &code, &nbytes, &url );
429 if (n == 0)
430 {
431 fprintf( stderr, "%s: line %ld can not be parsed\n", logfilename, nlines_sent );
432 errors++;
433 if (errors > 10)
434 return 0;
435 continue;
436 }
437
438 n = sprintf( output, "%s %s %s\n", code, nbytes, url );
439 nbytes_sent += (long) n;
440
441 if (verbose && (nlines_sent % 25000 == 0))
442 {
443 putchar( '.' );
444 fflush( stdout );
445 }
446
447 if (fast_puts( output, fp ) == EOF)
448 {
449 fprintf( stderr, "parseLogfile: while writing a temporary file fast_puts returned error: %s\n",
450 strerror(errno) );
451 return 0;
452 }
453 else
454 *total += n;
455 }
456
457 if (verbose)
458 printf( "\n" );
459
460 if (errors > 0)
461 {
462 fprintf( stderr, "There were %d parse errors.\n", errors );
463 fprintf( stderr, "Are you sure that a Squid log file (e.g. access.log) is specified ?\n" );
464 return 0;
465 }
466
467 if (nlines_sent < 5)
468 {
469 fprintf( stderr, "WARNING: the logfile \"%s\" has only %ld URLs which is not sufficient for an analysis.\n",
470 logfilename, nlines_sent );
471 }
472
473 printf( "logfile: %s read %3.1f MB, stored %3.1f MB (%ld URLs)\n",
474 logfilename, nbytes_read/(1024.0*1024.0), nbytes_sent/(1024.0*1024.0), nlines_sent );
475
476 return errors==0;
477 }
478
479
480 static const char * TLDs[] =
481 {
482 "ac", "ad", "ae", "aero", "af", "ag", "ai", "al",
483 "am", "an", "ao", "aq", "ar", "arpa", "as", "asia",
484 "at", "au", "aw", "ax", "az", "ba", "bb", "bd",
485 "be", "bf", "bg", "bh", "bi", "biz", "bj", "bm",
486 "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
487 "bz", "ca", "cat", "cc", "cd", "cf", "cg", "ch",
488 "ci", "ck", "cl", "cm", "cn", "co", "com", "coop",
489 "cr", "cu", "cv", "cx", "cy", "cz", "de", "dj",
490 "dk", "dm", "do", "dz", "ec", "edu", "ee", "eg",
491 "er", "es", "et", "eu", "fi", "fj", "fk", "fm",
492 "fo", "fr", "ga", "gb", "gd", "ge", "gf", "gg",
493 "gh", "gi", "gl", "gm", "gn", "gov", "gp", "gq",
494 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm",
495 "hn", "hr", "ht", "hu", "id", "ie", "il", "im",
496 "in", "info", "int", "io", "iq", "ir", "is", "it",
497 "je", "jm", "jo", "jobs", "jp", "ke", "kg", "kh",
498 "ki", "km", "kn", "kp", "kr", "kw", "ky", "kz",
499 "la", "lb", "lc", "li", "lk", "lr", "ls", "lt",
500 "lu", "lv", "ly", "ma", "mc", "md", "me", "mg",
501 "mh", "mil", "mk", "ml", "mm", "mn", "mo", "mobi",
502 "mp", "mq", "mr", "ms", "mt", "mu", "museum", "mv",
503 "mw", "mx", "my", "mz", "na", "name", "nc", "ne",
504 "net", "nf", "ng", "ni", "nl", "no", "np", "nr",
505 "nu", "nz", "om", "org", "pa", "pe", "pf", "pg",
506 "ph", "pk", "pl", "pm", "pn", "pr", "pro", "ps",
507 "pt", "pw", "py", "qa", "re", "ro", "rs", "ru",
508 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh",
509 "si", "sj", "sk", "sl", "sm", "sn", "so", "sr",
510 "st", "su", "sv", "sy", "sz", "tc", "td", "tel",
511 "tf", "tg", "th", "tj", "tk", "tl", "tm", "tn",
512 "to", "tp", "tr", "travel", "tt", "tv", "tw", "tz",
513 "ua", "ug", "uk", "us", "uy", "uz", "va", "vc",
514 "ve", "vg", "vi", "vn", "vu", "wf", "ws", "xxx", "ye",
515 "yt", "za", "zm", "zw", NULL
516 };
517
validTLD(const char * line)518 static int validTLD(
519 const char * line )
520 {
521 const char * tld;
522 const char ** t;
523
524 tld = strrchr( line, '.' );
525 if (tld == NULL)
526 return 0;
527
528 tld++;
529 for (t = TLDs; *t != NULL; t++)
530 {
531 if (strcasecmp( *t, tld ) == 0)
532 return 1;
533 }
534 return 0;
535 }
536
537
main(int argc,char * argv[])538 int main( int argc, char * argv[] )
539 {
540 char opt;
541 int counter;
542 int i;
543 int n;
544 int tmpf;
545 FILE * tmpfp;
546 int s;
547 FILE * fp;
548 int numlogs;
549 int apachelog;
550 int verbose;
551 char * tmpdir;
552 unsigned long total_written;
553 char domain[128];
554 char email[128];
555 char fullname[128];
556 char logfile[64][1024];
557 char tempfilename[256];
558 char header[2048];
559 char buffer[32*1024];
560 struct timeval tv;
561
562 logfile[0][0] = email[0] = domain[0] = fullname[0] = '\0';
563 numlogs = 0;
564 apachelog = 0;
565 verbose = 0;
566
567 setenv( "POSIXLY_CORRECT", "POSIX.2", 1 );
568 while ((opt = getopt( argc, argv, "?val:d:e:n:" )) > 0)
569 {
570 switch (opt)
571 {
572 case 'a':
573 apachelog = 1;
574 break;
575 case 'd':
576 strcpy( domain, optarg );
577 break;
578 case 'e':
579 strcpy( email, optarg );
580 break;
581 case 'n':
582 strcpy( fullname, optarg );
583 break;
584 case 'l':
585 if (numlogs == 64)
586 {
587 fprintf( stderr, "only 64 logfiles can be analysed.\n" );
588 exit( 1 );
589 }
590 strcpy( logfile[numlogs], optarg );
591 numlogs++;
592 break;
593 case 'v':
594 verbose++;
595 break;
596 case '?':
597 default:
598 usage();
599 break;
600 }
601 }
602
603 if (fullname[0] == '\0' || numlogs == 0 || email[0] == '\0' || domain[0] == '\0')
604 usage();
605
606 if (strchr( email, '@' ) == NULL ||
607 strchr( email, '.' ) == NULL ||
608 strstr( email, "example.com" ) != NULL ||
609 !validTLD(email))
610 {
611 fprintf( stderr, "The report is emailed to you so you must supply a correct and valid email address\n" );
612 fprintf( stderr, "No data is uploaded. No report will be emailed.\n" );
613 exit( 1 );
614 }
615
616 fp = fopen( logfile[0], "r" );
617 if (fp == NULL)
618 {
619 fprintf( stderr, "cannot read Squid logfile \"%s\"\n", logfile[0] );
620 exit( 1 );
621 }
622 fclose( fp );
623
624 printf( "The results will be sent by the support desk via email to %s\n", email );
625
626
627 #if 0
628 printf( "header sent to %s\n", UFDB_UPLOAD_UNCATEGORISED_URLS_WEBSITE );
629 printf( "%s", header );
630 #endif
631
632 /* TO-DO: also send local always-allow and always-block categories */
633
634 tmpdir = getenv( "TMPDIR" );
635 if (tmpdir == NULL)
636 tmpdir = (char*) "/tmp";
637 sprintf( tempfilename, "%s/ufdb-analyse-%d-%ld", tmpdir, getpid(), random() % 9999 );
638 tmpfp = fopen( tempfilename, "w" );
639 if (tmpfp == NULL)
640 {
641 fprintf( stderr, "%s: cannot open temporary file.\n", tempfilename );
642 exit( 1 );
643 }
644 if (verbose)
645 printf( "processed URLs are written to temporary file %s\n", tempfilename );
646
647 total_written = 0;
648 for (i = 0; i < numlogs; i++)
649 {
650 if (!parseLogfile( tmpfp, logfile[i], apachelog, verbose, &total_written ))
651 {
652 fprintf( stderr, "Too many errors occurred. Upload was interrupted.\n" );
653 fclose( tmpfp );
654 unlink( tempfilename );
655 exit( 1 );
656 }
657 }
658 fclose( tmpfp );
659
660 if (verbose)
661 printf( "the URLs are in temporary file %s\n", tempfilename );
662
663 tmpf = open( tempfilename, O_RDONLY|O_EXCL, 0600 );
664 if (tmpf < 0)
665 {
666 fprintf( stderr, "%s: cannot open temporary file for reading: %s\n", tempfilename, strerror(errno) );
667 exit( 1 );
668 }
669
670 /* TO-DO: generate a token, upload it and use the unique token in the feedback email */
671
672 if (verbose)
673 printf( "going to upload the URLs (%3.2lf MB)\n", total_written/(1024.0*1024.0) );
674
675 s = UFDBopenSocket( UFDB_UPLOAD_UNCATEGORISED_URLS_WEBSITE, 80 );
676 if (s < 0)
677 {
678 fprintf( stderr, "cannot open communication socket with http://%s %s\n",
679 UFDB_UPLOAD_UNCATEGORISED_URLS_WEBSITE, strerror(errno) );
680 unlink( tempfilename );
681 exit( 1 );
682 }
683 tv.tv_sec = 600;
684 tv.tv_usec = 0;
685 setsockopt( s, SOL_SOCKET, SO_RCVTIMEO, (void *) &tv, sizeof(tv) );
686 tv.tv_sec = 600;
687 tv.tv_usec = 0;
688 setsockopt( s, SOL_SOCKET, SO_SNDTIMEO, (void *) &tv, sizeof(tv) );
689
690 sprintf( header, "POST " UFDB_UPLOAD_ANALYSE_SQUID_LOG_CGI " HTTP/1.0\r\n"
691 "Host: " UFDB_UPLOAD_UNCATEGORISED_URLS_WEBSITE "\r\n"
692 "Accept: */*\r\n"
693 "User-Agent: ufdbAnalyse-" UFDB_VERSION "\r\n"
694 "Content-Type: text/plain\r\n"
695 "Content-Length: %ld\r\n"
696 "Connection: close\r\n"
697 "X-mydomain: %s\r\n"
698 "X-myemail: %s\r\n"
699 "X-fullname: %s\r\n"
700 "\r\n",
701 total_written,
702 domain,
703 email,
704 fullname );
705 n = strlen( header );
706 if (write( s, header, n ) != n)
707 {
708 fprintf( stderr, "cannot write header to http://%s\n", UFDB_UPLOAD_UNCATEGORISED_URLS_WEBSITE );
709 unlink( tempfilename );
710 exit( 1 );
711 }
712
713 counter = 0;
714 while ((n = read( tmpf, buffer, 32*1024)) > 0)
715 {
716 i = write( s, buffer, n );
717 if (i < 0 || i != n)
718 {
719 printf( "error uploading URLs: counter=%d i=%d n=%d %s\n", counter, i, n,
720 i > 0 ? "incomplete write" : strerror(errno) );
721 break;
722 }
723 counter++;
724 if (verbose && counter % 32 == 0)
725 {
726 putchar( '.' );
727 fflush( stdout );
728 }
729 }
730
731 if (verbose)
732 putchar( '\n' );
733
734 /* for every 10 MB sleep a second */
735 n = total_written / (10 * 1024 * 1024);
736 if (n < 1)
737 n = 1;
738 if (n > 5)
739 n = 5;
740 if (verbose)
741 printf( "Waiting for a response from the server ...\n" );
742 sleep( n );
743
744 n = read( s, buffer, 4096 );
745 if (n < 0)
746 {
747 printf( "error reading the server response: %s\n", strerror(errno) );
748 unlink( tempfilename );
749 exit( 1 );
750 }
751 buffer[n] = '\0';
752 if (sscanf( buffer, "HTTP/%d.%d %d", &i, &i, &n ) != 3)
753 {
754 printf( "error parsing server response:\n%s\n", buffer );
755 unlink( tempfilename );
756 exit( 1 );
757 }
758 if (verbose)
759 printf( "server response status code is %d (%s)", n, n==200 ? "OK" : "something is wrong" );
760
761 if (n != 200)
762 {
763 printf( "The server responded with an unexpected status code %d. Contact the support desk.\n", n );
764 unlink( tempfilename );
765 exit( 1 );
766 }
767
768 /* TO-DO: the server must send a unique ID and it must be parsed and printed. */
769 /* TO-DO: the unique ID can be used in future references with the helpdesk */
770
771 close( s );
772 close( tmpf );
773 unlink( tempfilename );
774
775 exit( 0 );
776 /*NOTREACHED*/
777 return 0;
778 }
779
780
781 #ifdef __cplusplus
782 }
783 #endif
784