/* * gnome-score.c * originally by Elliot Lee, subsequently bashed around by Nathan Bryant */ #include #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_FSUID_H #ifdef HAVE_SETFSGID #include #endif #endif #include "gnome-defs.h" #include "gnome-score.h" #include "gnome-util.h" #ifndef NSCORES #define NSCORES 10 #endif #define SCORE_PATH GNOMELOCALSTATEDIR "/games" struct command { gfloat score; int level; /* length of level arg to gnome_score_log * including null term */ gboolean ordering; }; struct ascore_t { gchar *username; time_t scoretime; gfloat score; }; static int outfd = -1, infd = -1; static const gchar *defgamename; /********************** internal functions ***********************************/ static gchar * gnome_get_score_file_name (const gchar * progname, const gchar * level) { if (level) return g_strconcat (SCORE_PATH "/", progname, ".", level, ".scores", NULL); else return g_strconcat (SCORE_PATH "/", progname, ".scores", NULL); } static void print_ascore (struct ascore_t *ascore, FILE * outfile) { fprintf (outfile, "%f %ld %s\n", ascore->score, (long int) ascore->scoretime, ascore->username); } static void free_ascore(struct ascore_t *data) { g_free(data->username); g_free(data); } /**** log_score Inputs: 'progname' - the program to log a score for 'level' - the freeform level identifier 'username' - the username that this score is logged under 'score' - the game score 'ordering' - whether lower scores are better. Outputs: 'retval' - Whether the score got onto the highscore list Description: Loads all the existing scores into the 'scores' list. Goes through and finds out whether there's a place for the new score on the high-score list, and if so, inserts it. Writes out the new high-score list. */ static gint log_score (const gchar * progname, const gchar * level, gchar * username, gfloat score, gboolean ordering) { FILE *infile; FILE *outfile; gchar buf[512], name[512]; GList *scores = NULL, *anode; gchar *game_score_file; gfloat ascore; time_t atime; struct ascore_t *anitem, *curscore; int i; gint retval = 1; gint pos; game_score_file = gnome_get_score_file_name (progname, level); infile = fopen (game_score_file, "r"); if (infile) { while(fgets(buf, sizeof(buf), infile)) { i = strlen (buf) - 1; /* Chomp */ while (isspace (buf[i])) buf[i--] = '\0'; if(sscanf(buf, "%f %ld %s", &ascore, &atime, name) != 3) break; anitem = g_new(struct ascore_t, 1); anitem->score = ascore; anitem->username = g_strdup (name); anitem->scoretime = atime; scores = g_list_append (scores, (gpointer) anitem); } fclose (infile); } anitem = g_new(struct ascore_t, 1); anitem->score = score; anitem->username = g_strdup (username); anitem->scoretime = time (NULL); for (pos = 0, anode = scores; pos < NSCORES && anode; pos++, anode = anode->next) { curscore = anode->data; if (ordering) { if (curscore->score < anitem->score) break; } else { if (curscore->score > anitem->score) break; } } if (pos < NSCORES) { scores = g_list_insert (scores, anitem, pos); if ((anode = g_list_nth (scores, NSCORES))) { free_ascore (anode->data); scores = g_list_remove_link (scores, g_list_nth (scores, NSCORES)); } retval = pos + 1; } else retval = 0; /* we dont create the file; it must already exist */ truncate (game_score_file, 0); outfile = fopen (game_score_file, "r+"); if (outfile) { g_list_foreach (scores, (GFunc) print_ascore, outfile); fclose (outfile); } else perror (game_score_file); g_free (game_score_file); g_list_foreach (scores, (GFunc) free_ascore, NULL); g_list_free (scores); return retval; } static int gnome_score_child (void) { struct command cmd; gchar *level; struct passwd *pwd; gint retval; #ifdef HAVE_SETFSGID gid_t gid; gid = getegid (); setgid (getgid ()); setfsgid (gid); #endif pwd = getpwuid (getuid ()); while (read (STDIN_FILENO, &cmd, sizeof cmd) == sizeof(cmd)) { level = g_new (char, cmd.level); if (read (STDIN_FILENO, level, cmd.level) != cmd.level) return EXIT_FAILURE; if (!*level) { g_free(level); level = NULL; } retval = log_score (defgamename, level, pwd->pw_name, cmd.score, cmd.ordering); if (write(STDOUT_FILENO, &retval, sizeof retval) != sizeof retval) return EXIT_FAILURE; if (level) g_free(level); } return EXIT_SUCCESS; } static void drop_perms (void) { gid_t gid = getegid (); setregid (getgid (), getgid ()); /* on some os'es (eg linux) this * incantation will also drop the * saved gid */ /* see if we can set it back -- if we can, saved id wasnt dropped */ if (gid != getgid() && !setgid (gid)) { if (getuid()) g_warning ("losing saved gid implementation detected, " "get a real OS :)\n"); setgid (getgid ()); } } /*********************** external functions **********************************/ /** * gnome_score_init: * @gamename: Identifies the game name. * * GNOME Games should call this routine as the first statement * in main () if they have been installed setgid to the games() group. * * Returns: 0 on success, returns -1 on failure. * * group privileges are dropped regardless of the status returned. */ gint gnome_score_init (const gchar * gamename) { int inpipe[2], outpipe[2]; /* *creates a child process with which we communicate through a pair of pipes, * then drops privileges. */ if (!gamename) gamename = ""; if (!(defgamename = g_strdup (gamename)) || pipe(inpipe)) { drop_perms(); return -1; } if (pipe (outpipe)) { close (inpipe[0]); close (inpipe[1]); drop_perms (); return -1; } outfd = outpipe[1]; infd = inpipe[0]; switch (fork ()) { case 0: if (dup2 (outpipe[0], STDIN_FILENO) == -1 || dup2 (inpipe[1], STDOUT_FILENO) == -1) exit (EXIT_FAILURE); close(inpipe[0]); close(inpipe[1]); close(outpipe[0]); close(outpipe[1]); exit (gnome_score_child ()); case -1: close (inpipe[0]); close (inpipe[1]); close (outpipe[0]); close (outpipe[1]); infd = outfd = -1; drop_perms (); return -1; } close(outpipe[0]); close(inpipe[1]); drop_perms (); return 0; } /** * gnome_score_log: * @score: the score achieved by the user in this game * @level: level on which the score was obtained * @higher_to_lower_score_order: biggers is better or not * * Logs a score entry for the user. * * Returns 0 on failure or the status from the gnome-score helper * program. */ gint gnome_score_log (gfloat score, gchar * level, gboolean higher_to_lower_score_order) { struct command cmd; gint retval; if (getgid () != getegid ()) { g_error ("gnome_score_init must be called first thing in main()\n"); abort (); } if (infd == -1 || outfd == -1) return 0; cmd.score = score; if (!level) level = ""; cmd.level = strlen (level) + 1; cmd.ordering = higher_to_lower_score_order; if (write (outfd, &cmd, sizeof cmd) != sizeof(cmd) || write (outfd, level, cmd.level) != cmd.level || read (infd, &retval, sizeof retval) != sizeof(retval)) { close (outfd); close (infd); infd = outfd = -1; return 0; } return retval; } /** * gnome_score_get_notable: * @gamename: the name of the game we want to fetch information from. * @level: the level from which we want to pull information. * @names: an array of strings is returned at the address pointed here * @scores: an array of gfloats is returned at the address pointed here * @scoretimes: an array of time_t is returned at the address pointed here * * Fetches the most notable players on @gamename at level @level. * * Returns the number of scores returned. @names, @scores and @scoretime * point to regions that were allocated with g_malloc() with the contents. */ gint gnome_score_get_notable (gchar * gamename, gchar * level, gchar *** names, gfloat ** scores, time_t ** scoretimes) { const gchar *realname; gchar buf[512], *buf2; gchar *infile_name; FILE *infile; gint retval; g_return_val_if_fail (names != NULL, 0); g_return_val_if_fail (scores != NULL, 0); if (gamename == NULL) realname = defgamename; else realname = gamename; infile_name = gnome_get_score_file_name (realname, level); infile = fopen (infile_name, "r"); g_free (infile_name); if (infile) { *names = g_malloc ((NSCORES + 1) * sizeof (gchar *)); *scores = g_malloc ((NSCORES + 1) * sizeof (gfloat)); *scoretimes = g_malloc ((NSCORES + 1) * sizeof (time_t)); for (retval = 0; fgets (buf, sizeof (buf), infile) && retval < NSCORES; retval++) { char *tokp; buf[strlen (buf) - 1] = 0; buf2 = strtok_r (buf, " ", &tokp); (*scores)[retval] = atof (buf2); buf2 = strtok_r (NULL, " ", &tokp); (*scoretimes)[retval] = atoi (buf2); buf2 = strtok_r (NULL, "\n", &tokp); (*names)[retval] = g_strdup (buf2); } (*names)[retval] = NULL; (*scores)[retval] = 0.0; fclose (infile); } else { *names = NULL; *scores = NULL; retval = 0; } return retval; }