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