QOF 0.8.4
|
00001 /********************************************************************\ 00002 * guid.c -- globally unique ID implementation * 00003 * Copyright (C) 2000 Dave Peticolas <peticola@cs.ucdavis.edu> * 00004 * * 00005 * This program is free software; you can redistribute it and/or * 00006 * modify it under the terms of the GNU General Public License as * 00007 * published by the Free Software Foundation; either version 2 of * 00008 * the License, or (at your option) any later version. * 00009 * * 00010 * This program is distributed in the hope that it will be useful, * 00011 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 00012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 00013 * GNU General Public License for more details. * 00014 * * 00015 * You should have received a copy of the GNU General Public License* 00016 * along with this program; if not, contact: * 00017 * * 00018 * Free Software Foundation Voice: +1-617-542-5942 * 00019 * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 * 00020 * Boston, MA 02110-1301, USA gnu@gnu.org * 00021 * * 00022 \********************************************************************/ 00023 00024 # include <config.h> 00025 00026 #ifdef HAVE_SYS_TYPES_H 00027 # include <sys/types.h> 00028 #endif 00029 #include <ctype.h> 00030 #include <dirent.h> 00031 #include <glib.h> 00032 #include <stdlib.h> 00033 #include <stdio.h> 00034 #include <string.h> 00035 #include <sys/stat.h> 00036 #ifdef HAVE_SYS_TIMES_H 00037 # include <sys/times.h> 00038 #endif 00039 #include <time.h> 00040 #include <unistd.h> 00041 #include "qof.h" 00042 #include "md5.h" 00043 00044 # ifndef P_tmpdir 00045 # define P_tmpdir "/tmp" 00046 # endif 00047 00048 /* Constants *******************************************************/ 00049 #define DEBUG_GUID 0 00050 #define BLOCKSIZE 4096 00051 #define THRESHOLD (2 * BLOCKSIZE) 00052 00053 00054 /* Static global variables *****************************************/ 00055 static gboolean guid_initialized = FALSE; 00056 static struct md5_ctx guid_context; 00057 00058 /* This static indicates the debugging module that this .o belongs to. */ 00059 static QofLogModule log_module = QOF_MOD_ENGINE; 00060 00061 /* Memory management routines ***************************************/ 00062 00063 GUID * 00064 guid_malloc (void) 00065 { 00066 return g_slice_new (GUID); 00067 } 00068 00069 void 00070 guid_free (GUID * guid) 00071 { 00072 if (!guid) 00073 return; 00074 00075 g_slice_free (GUID, guid); 00076 } 00077 00078 const GUID * 00079 guid_null (void) 00080 { 00081 static int null_inited = 0; 00082 static GUID null_guid; 00083 00084 if (!null_inited) 00085 { 00086 int i; 00087 char *tmp = "NULLGUID.EMPTY."; 00088 00089 /* 16th space for '\O' */ 00090 for (i = 0; i < GUID_DATA_SIZE; i++) 00091 null_guid.data[i] = tmp[i]; 00092 00093 null_inited = 1; 00094 } 00095 00096 return &null_guid; 00097 } 00098 00099 /* Function implementations ****************************************/ 00100 00101 /* This code is based on code in md5.c in GNU textutils. */ 00102 static size_t 00103 init_from_stream (FILE * stream, size_t max_size) 00104 { 00105 char buffer[BLOCKSIZE + 72]; 00106 size_t sum, block_size, total; 00107 00108 if (max_size <= 0) 00109 return 0; 00110 00111 total = 0; 00112 00113 /* Iterate over file contents. */ 00114 while (1) 00115 { 00116 /* We read the file in blocks of BLOCKSIZE bytes. One call of the 00117 * computation function processes the whole buffer so that with the 00118 * next round of the loop another block can be read. */ 00119 size_t n; 00120 sum = 0; 00121 00122 if (max_size < BLOCKSIZE) 00123 block_size = max_size; 00124 else 00125 block_size = BLOCKSIZE; 00126 00127 /* Read block. Take care for partial reads. */ 00128 do 00129 { 00130 n = fread (buffer + sum, 1, block_size - sum, stream); 00131 00132 sum += n; 00133 } 00134 while (sum < block_size && n != 0); 00135 00136 max_size -= sum; 00137 00138 if (n == 0 && ferror (stream)) 00139 return total; 00140 00141 /* If end of file or max_size is reached, end the loop. */ 00142 if ((n == 0) || (max_size == 0)) 00143 break; 00144 00145 /* Process buffer with BLOCKSIZE bytes. Note that 00146 * BLOCKSIZE % 64 == 0 */ 00147 md5_process_block (buffer, BLOCKSIZE, &guid_context); 00148 00149 total += sum; 00150 } 00151 00152 /* Add the last bytes if necessary. */ 00153 if (sum > 0) 00154 { 00155 md5_process_bytes (buffer, sum, &guid_context); 00156 total += sum; 00157 } 00158 00159 return total; 00160 } 00161 00162 static size_t 00163 init_from_file (const char *filename, size_t max_size) 00164 { 00165 struct stat stats; 00166 size_t total = 0; 00167 size_t file_bytes; 00168 FILE *fp; 00169 00170 memset (&stats, 0, sizeof (stats)); 00171 if (stat (filename, &stats) != 0) 00172 return 0; 00173 00174 md5_process_bytes (&stats, sizeof (stats), &guid_context); 00175 total += sizeof (stats); 00176 00177 if (max_size <= 0) 00178 return total; 00179 00180 fp = fopen (filename, "r"); 00181 if (fp == NULL) 00182 return total; 00183 00184 file_bytes = init_from_stream (fp, max_size); 00185 00186 PINFO ("guid_init got %llu bytes from %s", 00187 (unsigned long long int) file_bytes, filename); 00188 00189 total += file_bytes; 00190 00191 fclose (fp); 00192 00193 return total; 00194 } 00195 00196 static size_t 00197 init_from_dir (const char *dirname, unsigned int max_files) 00198 { 00199 char filename[1024]; 00200 struct dirent *de; 00201 struct stat stats; 00202 size_t total; 00203 int result; 00204 DIR *dir; 00205 00206 if (max_files <= 0) 00207 return 0; 00208 00209 dir = opendir (dirname); 00210 if (dir == NULL) 00211 return 0; 00212 00213 total = 0; 00214 00215 do 00216 { 00217 de = readdir (dir); 00218 if (de == NULL) 00219 break; 00220 00221 md5_process_bytes (de->d_name, strlen (de->d_name), &guid_context); 00222 total += strlen (de->d_name); 00223 00224 result = snprintf (filename, sizeof (filename), 00225 "%s/%s", dirname, de->d_name); 00226 if ((result < 0) || (result >= (int) sizeof (filename))) 00227 continue; 00228 00229 memset (&stats, 0, sizeof (stats)); 00230 if (stat (filename, &stats) != 0) 00231 continue; 00232 md5_process_bytes (&stats, sizeof (stats), &guid_context); 00233 total += sizeof (stats); 00234 00235 max_files--; 00236 } 00237 while (max_files > 0); 00238 00239 closedir (dir); 00240 00241 return total; 00242 } 00243 00244 static size_t 00245 init_from_time (void) 00246 { 00247 size_t total; 00248 time_t t_time; 00249 #ifdef HAVE_SYS_TIMES_H 00250 clock_t clocks; 00251 struct tms tms_buf; 00252 #endif 00253 00254 total = 0; 00255 00256 t_time = time (NULL); 00257 md5_process_bytes (&t_time, sizeof (t_time), &guid_context); 00258 total += sizeof (t_time); 00259 00260 #ifdef HAVE_SYS_TIMES_H 00261 clocks = times (&tms_buf); 00262 md5_process_bytes (&clocks, sizeof (clocks), &guid_context); 00263 md5_process_bytes (&tms_buf, sizeof (tms_buf), &guid_context); 00264 total += sizeof (clocks) + sizeof (tms_buf); 00265 #endif 00266 00267 return total; 00268 } 00269 00270 static size_t 00271 init_from_int (int val) 00272 { 00273 md5_process_bytes (&val, sizeof (val), &guid_context); 00274 return sizeof (int); 00275 } 00276 00277 static size_t 00278 init_from_buff (unsigned char *buf, size_t buflen) 00279 { 00280 md5_process_bytes (buf, buflen, &guid_context); 00281 return buflen; 00282 } 00283 00284 void 00285 guid_init (void) 00286 { 00287 size_t bytes = 0; 00288 00289 /* Not needed; taken care of on first malloc. 00290 * guid_memchunk_init(); */ 00291 00292 md5_init_ctx (&guid_context); 00293 00294 /* entropy pool */ 00295 bytes += init_from_file ("/dev/urandom", 512); 00296 00297 /* files */ 00298 { 00299 const char *files[] = { "/etc/passwd", 00300 "/proc/loadavg", 00301 "/proc/meminfo", 00302 "/proc/net/dev", 00303 "/proc/rtc", 00304 "/proc/self/environ", 00305 "/proc/self/stat", 00306 "/proc/stat", 00307 "/proc/uptime", 00308 NULL 00309 }; 00310 int i; 00311 00312 for (i = 0; files[i] != NULL; i++) 00313 bytes += init_from_file (files[i], BLOCKSIZE); 00314 } 00315 00316 /* directories */ 00317 { 00318 const char *dirname; 00319 const char *dirs[] = { 00320 "/proc", 00321 P_tmpdir, 00322 "/var/lock", 00323 "/var/log", 00324 "/var/mail", 00325 "/var/spool/mail", 00326 "/var/run", 00327 NULL 00328 }; 00329 int i; 00330 00331 for (i = 0; dirs[i] != NULL; i++) 00332 bytes += init_from_dir (dirs[i], 32); 00333 00334 dirname = g_get_home_dir (); 00335 if (dirname != NULL) 00336 bytes += init_from_dir (dirname, 32); 00337 } 00338 00339 /* process and parent ids */ 00340 { 00341 pid_t pid; 00342 00343 pid = getpid (); 00344 md5_process_bytes (&pid, sizeof (pid), &guid_context); 00345 bytes += sizeof (pid); 00346 00347 #ifdef HAVE_GETPPID 00348 pid = getppid (); 00349 md5_process_bytes (&pid, sizeof (pid), &guid_context); 00350 bytes += sizeof (pid); 00351 #endif 00352 } 00353 00354 /* user info */ 00355 { 00356 #ifdef HAVE_GETUID 00357 uid_t uid; 00358 gid_t gid; 00359 char *s; 00360 00361 s = getlogin (); 00362 if (s != NULL) 00363 { 00364 md5_process_bytes (s, strlen (s), &guid_context); 00365 bytes += strlen (s); 00366 } 00367 00368 uid = getuid (); 00369 md5_process_bytes (&uid, sizeof (uid), &guid_context); 00370 bytes += sizeof (uid); 00371 00372 gid = getgid (); 00373 md5_process_bytes (&gid, sizeof (gid), &guid_context); 00374 bytes += sizeof (gid); 00375 #endif 00376 } 00377 00378 /* host info */ 00379 { 00380 #ifdef HAVE_GETHOSTNAME 00381 char string[1024]; 00382 00383 memset (string, 0, sizeof (string)); 00384 gethostname (string, sizeof (string)); 00385 md5_process_bytes (string, sizeof (string), &guid_context); 00386 bytes += sizeof (string); 00387 #endif 00388 } 00389 00390 /* plain old random */ 00391 { 00392 int n, i; 00393 00394 srand ((unsigned int) time (NULL)); 00395 00396 for (i = 0; i < 32; i++) 00397 { 00398 n = rand (); 00399 00400 md5_process_bytes (&n, sizeof (n), &guid_context); 00401 bytes += sizeof (n); 00402 } 00403 } 00404 00405 /* time in secs and clock ticks */ 00406 bytes += init_from_time (); 00407 00408 PINFO ("got %llu bytes", (unsigned long long int) bytes); 00409 00410 if (bytes < THRESHOLD) 00411 PWARN ("only got %llu bytes.\n" 00412 "The identifiers might not be very random.\n", 00413 (unsigned long long int) bytes); 00414 00415 guid_initialized = TRUE; 00416 } 00417 00418 void 00419 guid_init_with_salt (const void *salt, size_t salt_len) 00420 { 00421 guid_init (); 00422 00423 md5_process_bytes (salt, salt_len, &guid_context); 00424 } 00425 00426 void 00427 guid_init_only_salt (const void *salt, size_t salt_len) 00428 { 00429 md5_init_ctx (&guid_context); 00430 00431 md5_process_bytes (salt, salt_len, &guid_context); 00432 00433 guid_initialized = TRUE; 00434 } 00435 00436 void 00437 guid_shutdown (void) 00438 { 00439 } 00440 00441 #define GUID_PERIOD 5000 00442 00443 void 00444 guid_new (GUID * guid) 00445 { 00446 static int counter = 0; 00447 struct md5_ctx ctx; 00448 00449 if (guid == NULL) 00450 return; 00451 00452 if (!guid_initialized) 00453 guid_init (); 00454 00455 /* make the id */ 00456 ctx = guid_context; 00457 md5_finish_ctx (&ctx, guid->data); 00458 00459 /* update the global context */ 00460 init_from_time (); 00461 00462 /* Make it a little extra salty. I think init_from_time was buggy, 00463 * or something, since duplicate id's actually happened. Or something 00464 * like that. I think this is because init_from_time kept returning 00465 * the same values too many times in a row. So we'll do some 'block 00466 * chaining', and feed in the old guid as new random data. 00467 * 00468 * Anyway, I think the whole fact that I saw a bunch of duplicate 00469 * id's at one point, but can't reproduce the bug is rather alarming. 00470 * Something must be broken somewhere, and merely adding more salt 00471 * is just hiding the problem, not fixing it. 00472 */ 00473 init_from_int (433781 * counter); 00474 init_from_buff (guid->data, GUID_DATA_SIZE); 00475 00476 if (counter == 0) 00477 { 00478 FILE *fp; 00479 00480 fp = fopen ("/dev/urandom", "r"); 00481 if (fp == NULL) 00482 return; 00483 00484 init_from_stream (fp, 32); 00485 00486 fclose (fp); 00487 00488 counter = GUID_PERIOD; 00489 } 00490 00491 counter--; 00492 } 00493 00494 GUID 00495 guid_new_return (void) 00496 { 00497 GUID guid; 00498 00499 guid_new (&guid); 00500 00501 return guid; 00502 } 00503 00504 /* needs 32 bytes exactly, doesn't print a null char */ 00505 static void 00506 encode_md5_data (const unsigned char *data, char *buffer) 00507 { 00508 size_t count; 00509 00510 for (count = 0; count < GUID_DATA_SIZE; count++, buffer += 2) 00511 sprintf (buffer, "%02x", data[count]); 00512 } 00513 00514 /* returns true if the first 32 bytes of buffer encode 00515 * a hex number. returns false otherwise. Decoded number 00516 * is packed into data in little endian order. */ 00517 static gboolean 00518 decode_md5_string (const unsigned char *string, unsigned char *data) 00519 { 00520 unsigned char n1, n2; 00521 size_t count = -1; 00522 unsigned char c1, c2; 00523 00524 if (NULL == data) 00525 return FALSE; 00526 if (NULL == string) 00527 goto badstring; 00528 00529 for (count = 0; count < GUID_DATA_SIZE; count++) 00530 { 00531 /* check for a short string e.g. null string ... */ 00532 if ((0 == string[2 * count]) || (0 == string[2 * count + 1])) 00533 goto badstring; 00534 00535 c1 = tolower (string[2 * count]); 00536 if (!isxdigit (c1)) 00537 goto badstring; 00538 00539 c2 = tolower (string[2 * count + 1]); 00540 if (!isxdigit (c2)) 00541 goto badstring; 00542 00543 if (isdigit (c1)) 00544 n1 = c1 - '0'; 00545 else 00546 n1 = c1 - 'a' + 10; 00547 00548 if (isdigit (c2)) 00549 n2 = c2 - '0'; 00550 else 00551 n2 = c2 - 'a' + 10; 00552 00553 data[count] = (n1 << 4) | n2; 00554 } 00555 return TRUE; 00556 00557 badstring: 00558 for (count = 0; count < GUID_DATA_SIZE; count++) 00559 { 00560 data[count] = 0; 00561 } 00562 return FALSE; 00563 } 00564 00565 /* Allocate the key */ 00566 00567 const char * 00568 guid_to_string (const GUID * guid) 00569 { 00570 #ifdef G_THREADS_ENABLED 00571 static GStaticPrivate guid_buffer_key = G_STATIC_PRIVATE_INIT; 00572 gchar *string; 00573 00574 string = g_static_private_get (&guid_buffer_key); 00575 if (string == NULL) 00576 { 00577 string = malloc (GUID_ENCODING_LENGTH + 1); 00578 g_static_private_set (&guid_buffer_key, string, g_free); 00579 } 00580 #else 00581 static char string[64]; 00582 #endif 00583 00584 encode_md5_data (guid->data, string); 00585 string[GUID_ENCODING_LENGTH] = '\0'; 00586 00587 return string; 00588 } 00589 00590 char * 00591 guid_to_string_buff (const GUID * guid, char *string) 00592 { 00593 if (!string || !guid) 00594 return NULL; 00595 00596 encode_md5_data (guid->data, string); 00597 00598 string[GUID_ENCODING_LENGTH] = '\0'; 00599 return &string[GUID_ENCODING_LENGTH]; 00600 } 00601 00602 gboolean 00603 string_to_guid (const char *string, GUID * guid) 00604 { 00605 return decode_md5_string ((const guchar *)string, (guid != NULL) ? guid->data : NULL); 00606 } 00607 00608 gboolean 00609 guid_equal (const GUID * guid_1, const GUID * guid_2) 00610 { 00611 if (guid_1 && guid_2) 00612 return (memcmp (guid_1, guid_2, GUID_DATA_SIZE) == 0); 00613 else 00614 return FALSE; 00615 } 00616 00617 gint 00618 guid_compare (const GUID * guid_1, const GUID * guid_2) 00619 { 00620 if (guid_1 == guid_2) 00621 return 0; 00622 00623 /* nothing is always less than something */ 00624 if (!guid_1 && guid_2) 00625 return -1; 00626 00627 if (guid_1 && !guid_2) 00628 return 1; 00629 00630 return memcmp (guid_1, guid_2, GUID_DATA_SIZE); 00631 } 00632 00633 guint 00634 guid_hash_to_guint (gconstpointer ptr) 00635 { 00636 const GUID *guid = ptr; 00637 guint hash = 0; 00638 unsigned int i, j; 00639 00640 if (!guid) 00641 { 00642 PERR ("received NULL guid pointer."); 00643 return 0; 00644 } 00645 00646 for (i = 0, j = 0; i < sizeof (guint); i++, j++) 00647 { 00648 if (j == GUID_DATA_SIZE) 00649 j = 0; 00650 00651 hash <<= 4; 00652 hash |= guid->data[j]; 00653 } 00654 00655 return hash; 00656 } 00657 00658 static gint 00659 guid_g_hash_table_equal (gconstpointer guid_a, gconstpointer guid_b) 00660 { 00661 return guid_equal (guid_a, guid_b); 00662 } 00663 00664 GHashTable * 00665 guid_hash_table_new (void) 00666 { 00667 return g_hash_table_new (guid_hash_to_guint, guid_g_hash_table_equal); 00668 }