From 123c5a87fa9cf6d9759ed7911d3544f05afdd11c Mon Sep 17 00:00:00 2001 From: Holger Schemel Date: Tue, 9 Mar 2010 23:35:25 +0100 Subject: [PATCH] rnd-20100309-2-src * fixed potential crash bug caused by illegal array access in engine snapshot loading and saving code * changed setting permissions of score files to be world-writable if the program is not installed and running setgid to allow the program to modify existing score files when run as a different user (which allows cheating, of course, as the score files are not protected against modification in this case) * added (commented out) suggestions for RO_GAME_DIR and RW_GAME_DIR to the top level Makefile for Debian / Ubuntu installations * added saving read-only levels from editor into personal level set (thanks to Bela Lubkin for the above four patches) --- ChangeLog | 11 +++ Makefile | 4 + src/conftime.h | 2 +- src/editor.c | 236 ++++++++++++++++++++++++++++++++++++-------- src/files.c | 4 + src/game.c | 12 +-- src/libgame/setup.c | 36 ++++++- src/main.h | 6 ++ 8 files changed, 256 insertions(+), 55 deletions(-) diff --git a/ChangeLog b/ChangeLog index 0d4ad4f7..96a007d5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -5,6 +5,17 @@ ".level"); to use this functionality, enter ":save-native-level" or ":snl" from the main menu with the native Supaplex level loaded and the appropriate tape loaded to the tape recorder + * fixed potential crash bug caused by illegal array access in engine + snapshot loading and saving code + * changed setting permissions of score files to be world-writable if + the program is not installed and running setgid to allow the program + to modify existing score files when run as a different user (which + allows cheating, of course, as the score files are not protected + against modification in this case) + * added (commented out) suggestions for RO_GAME_DIR and RW_GAME_DIR to + the top level Makefile for Debian / Ubuntu installations + * added saving read-only levels from editor into personal level set + (thanks to Bela Lubkin for the above four patches) 2010-03-03 * added updating of game values on the panel to Supaplex game engine diff --git a/Makefile b/Makefile index c157ab64..0ab0dfc4 100644 --- a/Makefile +++ b/Makefile @@ -22,10 +22,14 @@ X11_PATH = /usr/X11R6 # directory for read-only game data (like graphics, sounds, levels) # default is '.' to be able to run program without installation # RO_GAME_DIR = /usr/games +# use the following setting for Debian / Ubuntu installations: +# RO_GAME_DIR = /usr/share/games/rocksndiamonds # directory for writable game data (like highscore files) # default is '.' to be able to run program without installation # RW_GAME_DIR = /var/games +# use the following setting for Debian / Ubuntu installations: +# RW_GAME_DIR = /var/games/rocksndiamonds # uncomment if system has no joystick include file # JOYSTICK = -DNO_JOYSTICK diff --git a/src/conftime.h b/src/conftime.h index 303bcbd0..0a5f2859 100644 --- a/src/conftime.h +++ b/src/conftime.h @@ -1 +1 @@ -#define COMPILE_DATE_STRING "2010-03-09 14:59" +#define COMPILE_DATE_STRING "2010-03-09 23:35" diff --git a/src/editor.c b/src/editor.c index 1395f21c..bbc863ca 100644 --- a/src/editor.c +++ b/src/editor.c @@ -6630,8 +6630,12 @@ static boolean LevelChanged() boolean field_changed = FALSE; int x, y; +#if 1 + /* changed read-only levels can now be saved in personal level set */ +#else if (leveldir_current->readonly) return FALSE; +#endif for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++) @@ -6641,21 +6645,151 @@ static boolean LevelChanged() return (level.changed || field_changed); } -static boolean LevelContainsPlayer() +static boolean PrepareSavingIntoPersonalLevelSet() { - boolean player_found = FALSE; - int x, y; + static LevelDirTree *last_copied_leveldir = NULL; + static LevelDirTree *last_written_leveldir = NULL; + static int last_copied_level_nr = -1; + static int last_written_level_nr = -1; + LevelDirTree *leveldir_former = leveldir_current; + int level_nr_former = level_nr; + int new_level_nr; + + // remember last mod/save so that for current session, we write + // back to the same personal copy, asking only about overwrite. + if (leveldir_current == last_copied_leveldir && + level_nr == last_copied_level_nr) + { + // "cd" to personal level set dir (as used when writing last copy) + leveldir_current = last_written_leveldir; + level_nr = last_written_level_nr; - return TRUE; /* !!! CURRENTLY DEACTIVATED !!! */ + return TRUE; + } + + if (!Request("This level is read only ! " + "Save into personal level set ?", REQ_ASK)) + return FALSE; - for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++) + // "cd" to personal level set dir (for writing copy the first time) + leveldir_current = + getTreeInfoFromIdentifier(leveldir_first, getLoginName()); + + // find unused level number + for (new_level_nr = leveldir_current->first_level; ; new_level_nr++) { - if (Feld[x][y] == EL_PLAYER_1 || - Feld[x][y] == EL_SP_MURPHY) - player_found = TRUE; + static char *level_filename = NULL; + + setString(&level_filename, getDefaultLevelFilename(new_level_nr)); + + if (!fileExists(level_filename)) + break; } - return player_found; + last_copied_leveldir = leveldir_former; + last_copied_level_nr = level_nr_former; + + last_written_leveldir = leveldir_current; + last_written_level_nr = level_nr = new_level_nr; + + return TRUE; +} + +static void ModifyLevelInfoForSavingIntoPersonalLevelSet(char *former_name) +{ + static char *filename_levelinfo = NULL, *mod_name = NULL; + FILE *file; + + // annotate this copy-and-mod in personal levelinfo.conf + setString(&filename_levelinfo, + getPath2(getCurrentLevelDir(), LEVELINFO_FILENAME)); + + if ((file = fopen(filename_levelinfo, MODE_APPEND))) + { + fprintf(file, "\n"); + fprintf(file, "# level %d was modified from:\n", level_nr); + fprintf(file, "# - previous level set name: %s\n", + former_name); + fprintf(file, "# - level within previous set: %d \"%s\"\n", + level.file_info.nr, level.name); + fprintf(file, "# - previous author: %s\n", + level.author); + fprintf(file, "# - previous save date: "); + + if (level.creation_date.src == DATE_SRC_LEVELFILE) + { + fprintf(file, "%04d-%02d-%02d\n", + level.creation_date.year, + level.creation_date.month, + level.creation_date.day); + } + else + { + fprintf(file, "not recorded\n"); + } + + fclose(file); + } + + if (level_nr > leveldir_current->last_level) + { + static char *temp_levelinfo = NULL; + FILE *temp_file = NULL; + char line[MAX_LINE_LEN]; + + setString(&temp_levelinfo, + getPath2(getCurrentLevelDir(), + getStringCat2(LEVELINFO_FILENAME, ".new"))); + + if ((file = fopen(filename_levelinfo, MODE_READ)) && + (temp_file = fopen(temp_levelinfo, MODE_WRITE))) + { + while (fgets(line, MAX_LINE_LEN, file)) + { + if (!strPrefix(line, "levels:")) + fputs(line, temp_file); + else + fprintf(temp_file, "%-32s%d\n", "levels:", level_nr + 9); + } + } + + if (temp_file) + fclose(temp_file); + + if (file) + fclose(file); + + // needs error handling; also, ok on dos/win? + unlink(filename_levelinfo); + rename(temp_levelinfo, filename_levelinfo); + } + + // else: allow the save even if annotation failed + + // now... spray graffiti on the old level vital statistics + // user can change these; just trying to set a good baseline + + // don't truncate names for fear of making offensive or silly: + // long-named original author only recorded in levelinfo.conf. + // try to fit "Joe after Bob", "Joe (ed.)", then just "Joe" + if (!strEqual(level.author, leveldir_current->author)) + { + setString(&mod_name, getStringCat3(leveldir_current->author, + " after ", level.author)); + + if (strlen(mod_name) > MAX_LEVEL_AUTHOR_LEN) + setString(&mod_name, + getStringCat2(leveldir_current->author, " (ed.)")); + + if (strlen(mod_name) > MAX_LEVEL_AUTHOR_LEN) + setString(&mod_name, leveldir_current->author); + + strncpy(level.author, mod_name, MAX_LEVEL_AUTHOR_LEN); + + // less worried about truncation here + setString(&mod_name, getStringCat2("Mod: ", level.name)); + strncpy(level.name, mod_name, MAX_LEVEL_NAME_LEN); + } } static void CopyPlayfield(short src[MAX_LEV_FIELDX][MAX_LEV_FIELDY], @@ -11237,60 +11371,76 @@ static void HandleControlButtons(struct GadgetInfo *gi) break; case GADGET_ID_SAVE: - if (leveldir_current->readonly) - { - Request("This level is read only !", REQ_CONFIRM); + { + /* saving read-only levels into personal level set modifies global vars + "leveldir_current" and "level_nr"; restore them after saving level */ + LevelDirTree *leveldir_former = leveldir_current; + int level_nr_former = level_nr; + char *level_filename; + boolean new_level; + + if (leveldir_current->readonly && + !PrepareSavingIntoPersonalLevelSet()) break; - } - if (!LevelContainsPlayer()) - Request("No Level without Gregor Mc Duffin please !", REQ_CONFIRM); - else + level_filename = getDefaultLevelFilename(level_nr); + new_level = !fileExists(level_filename); + + if (new_level || + Request("Save this level and kill the old ?", REQ_ASK)) { - char *level_filename = getDefaultLevelFilename(level_nr); - boolean new_level = !fileExists(level_filename); + if (leveldir_former->readonly) + ModifyLevelInfoForSavingIntoPersonalLevelSet(leveldir_former->name); - if (new_level || - Request("Save this level and kill the old ?", REQ_ASK)) - { - CopyPlayfield(Feld, level.field); + CopyPlayfield(Feld, level.field); + SaveLevel(level_nr); - SaveLevel(level_nr); - } + level.changed = FALSE; if (new_level) - Request("Level saved !", REQ_CONFIRM); + { + char level_saved_msg[64]; - level.changed = FALSE; + if (leveldir_former->readonly) + sprintf(level_saved_msg, + "Level saved as level %d into personal level set !", + level_nr); + else + strcpy(level_saved_msg, "Level saved !"); + + Request(level_saved_msg, REQ_CONFIRM); + } } + + /* "cd" back to copied-from levelset (in case of saved read-only level) */ + leveldir_current = leveldir_former; + level_nr = level_nr_former; + break; + } case GADGET_ID_TEST: - if (!LevelContainsPlayer()) - Request("No Level without Gregor Mc Duffin please !", REQ_CONFIRM); - else - { - if (LevelChanged()) - level.game_version = GAME_VERSION_ACTUAL; + if (LevelChanged()) + level.game_version = GAME_VERSION_ACTUAL; - CopyPlayfield(level.field, FieldBackup); - CopyPlayfield(Feld, level.field); + CopyPlayfield(level.field, FieldBackup); + CopyPlayfield(Feld, level.field); - CopyNativeLevel_RND_to_Native(&level); + CopyNativeLevel_RND_to_Native(&level); - UnmapLevelEditorGadgets(); - UndrawSpecialEditorDoor(); + UnmapLevelEditorGadgets(); + UndrawSpecialEditorDoor(); - CloseDoor(DOOR_CLOSE_ALL); + CloseDoor(DOOR_CLOSE_ALL); - BackToFront(); /* force redraw of undrawn special door */ + BackToFront(); /* force redraw of undrawn special door */ - DrawCompleteVideoDisplay(); + DrawCompleteVideoDisplay(); - level_editor_test_game = TRUE; + level_editor_test_game = TRUE; + + StartGameActions(FALSE, setup.autorecord, level.random_seed); - StartGameActions(FALSE, setup.autorecord, level.random_seed); - } break; case GADGET_ID_EXIT: diff --git a/src/files.c b/src/files.c index 6304da1f..124b1db0 100644 --- a/src/files.c +++ b/src/files.c @@ -1346,6 +1346,8 @@ static struct DateInfo getCurrentDate() date.month = now->tm_mon + 1; date.day = now->tm_mday; + date.src = DATE_SRC_CLOCK; + return date; } @@ -2225,6 +2227,8 @@ static int LoadLevel_DATE(FILE *file, int chunk_size, struct LevelInfo *level) level->creation_date.month = getFile8Bit(file); level->creation_date.day = getFile8Bit(file); + level->creation_date.src = DATE_SRC_LEVELFILE; + return chunk_size; } diff --git a/src/game.c b/src/game.c index 46f050d2..d1983175 100644 --- a/src/game.c +++ b/src/game.c @@ -16440,8 +16440,8 @@ struct EngineSnapshotInfo int choice_pos[NUM_GROUP_ELEMENTS]; /* runtime values for belt position animations */ - int belt_graphic[4 * NUM_BELT_PARTS]; - int belt_anim_mode[4 * NUM_BELT_PARTS]; + int belt_graphic[4][NUM_BELT_PARTS]; + int belt_anim_mode[4][NUM_BELT_PARTS]; }; static struct EngineSnapshotInfo engine_snapshot_rnd; @@ -16481,8 +16481,8 @@ static void SaveEngineSnapshotValues_RND() int graphic = el2img(element); int anim_mode = graphic_info[graphic].anim_mode; - engine_snapshot_rnd.belt_graphic[i * 4 + j] = graphic; - engine_snapshot_rnd.belt_anim_mode[i * 4 + j] = anim_mode; + engine_snapshot_rnd.belt_graphic[i][j] = graphic; + engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode; } } } @@ -16510,8 +16510,8 @@ static void LoadEngineSnapshotValues_RND() { for (j = 0; j < NUM_BELT_PARTS; j++) { - int graphic = engine_snapshot_rnd.belt_graphic[i * 4 + j]; - int anim_mode = engine_snapshot_rnd.belt_anim_mode[i * 4 + j]; + int graphic = engine_snapshot_rnd.belt_graphic[i][j]; + int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j]; graphic_info[graphic].anim_mode = anim_mode; } diff --git a/src/libgame/setup.c b/src/libgame/setup.c index dbe124df..62ba9fc5 100644 --- a/src/libgame/setup.c +++ b/src/libgame/setup.c @@ -1410,21 +1410,41 @@ static int posix_mkdir(const char *pathname, mode_t mode) #endif } +static boolean posix_process_running_setgid() +{ +#if defined(PLATFORM_UNIX) + return (getgid() != getegid()); +#else + return FALSE; +#endif +} + void createDirectory(char *dir, char *text, int permission_class) { /* leave "other" permissions in umask untouched, but ensure group parts of USERDATA_DIR_MODE are not masked */ mode_t dir_mode = (permission_class == PERMS_PRIVATE ? DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC); - mode_t normal_umask = posix_umask(0); + mode_t last_umask = posix_umask(0); mode_t group_umask = ~(dir_mode & S_IRWXG); - posix_umask(normal_umask & group_umask); + int running_setgid = posix_process_running_setgid(); + + /* if we're setgid, protect files against "other" */ + /* else keep umask(0) to make the dir world-writable */ + + if (running_setgid) + posix_umask(last_umask & group_umask); + else + dir_mode |= MODE_W_ALL; if (!fileExists(dir)) if (posix_mkdir(dir, dir_mode) != 0) Error(ERR_WARN, "cannot create %s directory '%s'", text, dir); - posix_umask(normal_umask); /* reset normal umask */ + if (permission_class == PERMS_PUBLIC && !running_setgid) + chmod(dir, dir_mode); + + posix_umask(last_umask); /* restore previous umask */ } void InitUserDataDirectory() @@ -1434,8 +1454,14 @@ void InitUserDataDirectory() void SetFilePermissions(char *filename, int permission_class) { - chmod(filename, (permission_class == PERMS_PRIVATE ? - FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC)); + int running_setgid = posix_process_running_setgid(); + int perms = (permission_class == PERMS_PRIVATE ? + FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC); + + if (permission_class == PERMS_PUBLIC && !running_setgid) + perms |= MODE_W_ALL; + + chmod(filename, perms); } char *getCookie(char *file_type) diff --git a/src/main.h b/src/main.h index 19deaee0..9376b3ae 100644 --- a/src/main.h +++ b/src/main.h @@ -2287,6 +2287,12 @@ struct DateInfo int year; int month; int day; + + enum + { + DATE_SRC_CLOCK, + DATE_SRC_LEVELFILE + } src; }; struct LevelInfo -- 2.34.1