From 76a4f92e1815306a0abc3b8bf9ce56f48c7be8aa Mon Sep 17 00:00:00 2001 From: Eggbertx Date: Sat, 31 Dec 2022 01:51:14 -0800 Subject: [PATCH] Make config.Username useful (applies given username ownership to files) --- pkg/building/boards.go | 75 +++++++++++++++++++++++++++++++++------- pkg/building/building.go | 26 +++++++++----- pkg/building/catalog.go | 6 ++++ pkg/building/threads.go | 24 ++++++++----- pkg/config/util.go | 38 +++++++++++++++++++- pkg/posting/post.go | 26 ++++++++++---- 6 files changed, 157 insertions(+), 38 deletions(-) diff --git a/pkg/building/boards.go b/pkg/building/boards.go index 1954e028..6e1d28f0 100644 --- a/pkg/building/boards.go +++ b/pkg/building/boards.go @@ -160,12 +160,18 @@ func BuildBoardPages(board *gcsql.Board) error { boardPageFile, err = os.OpenFile(path.Join(criticalCfg.DocumentRoot, board.Dir, "1.html"), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777) if err != nil { - errEv.Err(err). + errEv.Err(err).Caller(). Str("page", "board.html"). - Caller().Msg("Failed getting board page") + Msg("Failed getting board page") return fmt.Errorf("failed opening /%s/board.html: %s", board.Dir, err.Error()) } defer boardPageFile.Close() + + if err = config.TakeOwnershipOfFile(boardPageFile); err != nil { + errEv.Err(err).Caller(). + Msg("Unable to take ownership of board.html") + return fmt.Errorf("unable to take ownership of /%s/board.html: %s", board.Dir, err.Error()) + } // Render board page template to the file, // packaging the board/section list, threads, and board info captchaCfg := config.GetSiteConfig().Captcha @@ -199,13 +205,17 @@ func BuildBoardPages(board *gcsql.Board) error { // catalog JSON file is built with the pages because pages are recorded in the JSON file catalogJSONFile, err := os.OpenFile(path.Join(criticalCfg.DocumentRoot, board.Dir, "catalog.json"), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777) if err != nil { - errEv.Err(err). - Str("subject", "catalog.json"). - Caller().Msg("Failed opening catalog.json") + errEv.Err(err).Caller(). + Msg("Failed opening catalog.json") return fmt.Errorf("failed opening /%s/catalog.json: %s", board.Dir, err.Error()) } defer catalogJSONFile.Close() + if err = config.TakeOwnershipOfFile(catalogJSONFile); err != nil { + errEv.Err(err).Caller(). + Msg("Unable to take ownership of catalog.json") + return fmt.Errorf("unable to take ownership of /%s/catalog.json: %s", board.Dir, err.Error()) + } for _, page := range catalog.pages { catalog.currentPage++ var currentPageFilepath string @@ -213,13 +223,20 @@ func BuildBoardPages(board *gcsql.Board) error { currentPageFilepath = path.Join(criticalCfg.DocumentRoot, board.Dir, pageFilename) currentPageFile, err = os.OpenFile(currentPageFilepath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777) if err != nil { - errEv.Err(err). + errEv.Err(err).Caller(). Str("page", pageFilename). - Caller().Msg("Failed getting board page") + Msg("Failed getting board page") continue } defer currentPageFile.Close() + if err = config.TakeOwnershipOfFile(currentPageFile); err != nil { + errEv.Err(err).Caller(). + Str("page", pageFilename). + Msg("Unable to update file ownership") + return errors.New("unable to set board page file ownership") + } + // Render the boardpage template captchaCfg := config.GetSiteConfig().Captcha if err = serverutil.MinifyTemplate(gctemplates.BoardPage, map[string]interface{}{ @@ -329,9 +346,19 @@ func buildBoard(board *gcsql.Board, force bool) error { return fmt.Errorf(pathExistsStr, dirPath) } if !dirInfo.IsDir() { + errEv.Err(os.ErrExist). + Str("dirPath", dirPath). + Caller().Send() return fmt.Errorf(dirIsAFileStr, dirPath) } } else if err = os.Mkdir(dirPath, 0666); err != nil { + errEv.Err(os.ErrExist). + Str("dirPath", dirPath). + Caller().Send() + return fmt.Errorf(genericErrStr, dirPath, err.Error()) + } + if err = config.TakeOwnership(dirPath); err != nil { + return fmt.Errorf(genericErrStr, dirPath, err.Error()) } @@ -356,7 +383,11 @@ func buildBoard(board *gcsql.Board, force bool) error { errEv.Err(err). Str("resPath", resPath). Caller().Send() - + return fmt.Errorf(genericErrStr, resPath, err.Error()) + } + if err = config.TakeOwnership(resPath); err != nil { + errEv.Err(err).Caller(). + Str("resPath", resPath).Send() return fmt.Errorf(genericErrStr, resPath, err.Error()) } @@ -382,6 +413,11 @@ func buildBoard(board *gcsql.Board, force bool) error { Caller().Send() return err } + if config.TakeOwnership(srcPath); err != nil { + errEv.Err(err).Caller(). + Str("srcPath", srcPath).Send() + return fmt.Errorf(genericErrStr, srcPath, err.Error()) + } if thumbInfo != nil { if !force { @@ -391,6 +427,13 @@ func buildBoard(board *gcsql.Board, force bool) error { return fmt.Errorf(dirIsAFileStr, thumbPath) } } else if err = os.Mkdir(thumbPath, 0666); err != nil { + errEv.Err(err).Caller(). + Str("thumbPath", thumbPath).Send() + return fmt.Errorf(genericErrStr, thumbPath, err.Error()) + } + if config.TakeOwnership(thumbPath); err != nil { + errEv.Err(err).Caller(). + Str("thumbPath", thumbPath).Send() return fmt.Errorf(genericErrStr, thumbPath, err.Error()) } @@ -424,13 +467,19 @@ func buildBoard(board *gcsql.Board, force bool) error { func BuildBoardListJSON() error { boardsJsonPath := path.Join(config.GetSystemCriticalConfig().DocumentRoot, "boards.json") boardListFile, err := os.OpenFile(boardsJsonPath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777) + errEv := gcutil.LogError(nil).Str("building", "boards.json") + defer errEv.Discard() if err != nil { - gcutil.LogError(err). - Str("building", "boardsList").Send() - return errors.New("Failed opening boards.json for writing: " + err.Error()) + errEv.Err(err).Caller().Send() + return errors.New("unable to open boards.json for writing: " + err.Error()) } defer boardListFile.Close() + if err = config.TakeOwnershipOfFile(boardListFile); err != nil { + errEv.Err(err).Caller().Send() + return errors.New("unable to update boards.json ownership: " + err.Error()) + } + boardsMap := map[string][]boardJSON{ "boards": {}, } @@ -452,12 +501,12 @@ func BuildBoardListJSON() error { // TODO: properly check if the board is in a hidden section boardJSON, err := json.Marshal(boardsMap) if err != nil { - gcutil.LogError(err).Str("building", "boards.json").Send() + errEv.Err(err).Caller().Send() return errors.New("Failed to create boards.json: " + err.Error()) } if _, err = serverutil.MinifyWriter(boardListFile, boardJSON, "application/json"); err != nil { - gcutil.LogError(err).Str("building", "boards.json").Send() + errEv.Err(err).Caller().Send() return errors.New("Failed writing boards.json file: " + err.Error()) } return nil diff --git a/pkg/building/building.go b/pkg/building/building.go index 2d4da800..1e5f69d1 100644 --- a/pkg/building/building.go +++ b/pkg/building/building.go @@ -112,6 +112,11 @@ func BuildFrontPage() error { } defer frontFile.Close() + if err = config.TakeOwnershipOfFile(frontFile); err != nil { + errEv.Err(err).Caller().Send() + return errors.New("Failed setting file ownership for front page: " + err.Error()) + } + var recentPostsArr []recentPost siteCfg := config.GetSiteConfig() recentPostsArr, err = getRecentPosts() @@ -164,8 +169,10 @@ func BuildPageFooter(writer io.Writer) (err error) { func BuildJS() error { // build consts.js from template err := gctemplates.InitTemplates("js") + errEv := gcutil.LogError(nil).Str("building", "consts.js") + defer errEv.Discard() if err != nil { - gcutil.LogError(err).Str("template", "consts.js").Send() + errEv.Err(err).Caller().Send() return errors.New("Error loading consts.js template:" + err.Error()) } @@ -174,13 +181,16 @@ func BuildJS() error { constsJSPath := path.Join(criticalCfg.DocumentRoot, "js", "consts.js") constsJSFile, err := os.OpenFile(constsJSPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) if err != nil { - gcutil.LogError(err). - Str("building", "consts.js"). - Str("filePath", constsJSPath).Send() - return fmt.Errorf("error opening %q for writing: %s", constsJSPath, err.Error()) + errEv.Err(err).Caller().Send() + return fmt.Errorf("error opening consts.js for writing: %s", err.Error()) } defer constsJSFile.Close() + if err = config.TakeOwnershipOfFile(constsJSFile); err != nil { + errEv.Err(err).Caller().Send() + return fmt.Errorf("unable to update file ownership for consts.js: %s", err.Error()) + } + if err = serverutil.MinifyTemplate(gctemplates.JsConsts, map[string]interface{}{ "webroot": criticalCfg.WebRoot, @@ -189,10 +199,8 @@ func BuildJS() error { "timezone": criticalCfg.TimeZone, }, constsJSFile, "text/javascript"); err != nil { - gcutil.LogError(err). - Str("building", "consts.js"). - Str("filePath", constsJSPath).Send() - return fmt.Errorf("error building %q: %s", constsJSPath, err.Error()) + errEv.Err(err).Caller().Send() + return fmt.Errorf("error building consts.js: %s", err.Error()) } return nil } diff --git a/pkg/building/catalog.go b/pkg/building/catalog.go index ca94244d..570c5e42 100644 --- a/pkg/building/catalog.go +++ b/pkg/building/catalog.go @@ -86,6 +86,12 @@ func BuildCatalog(boardID int) error { errEv.Err(err).Caller().Send() return fmt.Errorf("failed opening /%s/catalog.html: %s", board.Dir, err.Error()) } + defer catalogFile.Close() + + if err = config.TakeOwnershipOfFile(catalogFile); err != nil { + errEv.Err(err).Caller().Send() + return fmt.Errorf("failed taking ownership of /%s/catalog.html: %s", board.Dir, err.Error()) + } threadOPs, err := getBoardTopPosts(boardID) if err != nil { diff --git a/pkg/building/threads.go b/pkg/building/threads.go index 3374fdf4..dc987d2b 100644 --- a/pkg/building/threads.go +++ b/pkg/building/threads.go @@ -85,10 +85,15 @@ func BuildThreadPages(op *gcsql.Post) error { threadPageFilepath := path.Join(criticalCfg.DocumentRoot, board.Dir, "res", strconv.Itoa(op.ID)+".html") threadPageFile, err = os.OpenFile(threadPageFilepath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777) if err != nil { - errEv.Err(err). - Caller().Send() - return fmt.Errorf("unable to open opening /%s/res/%d.html: %s", board.Dir, op.ID, err.Error()) + errEv.Err(err).Caller().Send() + return fmt.Errorf("unable to open /%s/res/%d.html: %s", board.Dir, op.ID, err.Error()) } + defer threadPageFile.Close() + if err = config.TakeOwnershipOfFile(threadPageFile); err != nil { + errEv.Err(err).Caller().Send() + return fmt.Errorf("unable to set file permissions for /%s/res/%d.html: %s", board.Dir, op.ID, err.Error()) + } + errEv.Int("op", posts[0].ID) // render thread page captchaCfg := config.GetSiteConfig().Captcha @@ -103,8 +108,7 @@ func BuildThreadPages(op *gcsql.Post) error { "useCaptcha": captchaCfg.UseCaptcha() && !captchaCfg.OnlyNeededForThreads, "captcha": captchaCfg, }, threadPageFile, "text/html"); err != nil { - errEv.Err(err). - Caller().Send() + errEv.Err(err).Caller().Send() return fmt.Errorf("failed building /%s/res/%d threadpage: %s", board.Dir, posts[0].ID, err.Error()) } @@ -113,13 +117,16 @@ func BuildThreadPages(op *gcsql.Post) error { path.Join(criticalCfg.DocumentRoot, board.Dir, "res", strconv.Itoa(posts[0].ID)+".json"), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777) if err != nil { - errEv.Err(err). - Int("op", posts[0].ID). - Caller().Send() + errEv.Err(err).Caller().Send() return fmt.Errorf("failed opening /%s/res/%d.json: %s", board.Dir, posts[0].ID, err.Error()) } defer threadJSONFile.Close() + if err = config.TakeOwnershipOfFile(threadJSONFile); err != nil { + errEv.Err(err).Caller().Send() + return fmt.Errorf("failed setting file permissions for /%s/res/%d.json: %s", board.Dir, posts[0].ID, err.Error()) + } + threadMap := make(map[string][]Post) threadMap["posts"] = posts @@ -130,7 +137,6 @@ func BuildThreadPages(op *gcsql.Post) error { } if _, err = threadJSONFile.Write(threadJSON); err != nil { errEv.Err(err). - Int("op", posts[0].ID). Caller().Send() return fmt.Errorf("failed writing /%s/res/%d.json: %s", board.Dir, posts[0].ID, err.Error()) } diff --git a/pkg/config/util.go b/pkg/config/util.go index dbd0f4f2..20b77629 100644 --- a/pkg/config/util.go +++ b/pkg/config/util.go @@ -5,8 +5,11 @@ import ( "flag" "fmt" "os" + "os/user" "path" "reflect" + "runtime" + "strconv" "time" "github.com/gochan-org/gochan/pkg/gcutil" @@ -17,6 +20,8 @@ var ( "ListenIP", "Port", "Username", "UseFastCGI", "DocumentRoot", "TemplateDir", "LogDir", "DBtype", "DBhost", "DBname", "DBusername", "DBpassword", "SiteDomain", "Styles", } + uid int + gid int ) // MissingField represents a field missing from the configuration file @@ -74,6 +79,22 @@ func GetDefaultString(key string) string { return str } +func TakeOwnership(fp string) error { + if runtime.GOOS == "windows" || fp == "" { + // Chown returns an error in Windows + return nil + } + return os.Chown(fp, uid, gid) +} + +func TakeOwnershipOfFile(f *os.File) error { + if runtime.GOOS == "windows" || f == nil { + // Chown returns an error in Windows + return nil + } + return f.Chown(uid, gid) +} + // ParseJSON loads and parses JSON data, returning a GochanConfig pointer, any critical missing // fields that don't have defaults, and any error from parsing the file. This doesn't mean that the // values are valid, just that they exist @@ -148,7 +169,7 @@ func InitConfig(versionStr string) { Version: ParseVersion(versionStr), }, SiteConfig: SiteConfig{ - Username: "gochan", + Username: "", FirstPage: []string{"index.html", "firstrun.html", "1.html"}, Lockdown: false, LockdownMessage: "This imageboard has temporarily disabled posting. We apologize for the inconvenience", @@ -255,6 +276,21 @@ func InitConfig(versionStr string) { os.Exit(1) } + if runtime.GOOS != "windows" { + var gcUser *user.User + if cfg.Username != "" { + gcUser, err = user.Lookup(cfg.Username) + } else { + gcUser, err = user.Current() + } + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + uid, _ = strconv.Atoi(gcUser.Uid) + gid, _ = strconv.Atoi(gcUser.Gid) + } + if _, err = os.Stat(cfg.DocumentRoot); err != nil { fmt.Println(err.Error()) os.Exit(1) diff --git a/pkg/posting/post.go b/pkg/posting/post.go index 3cf84294..37d3f2fa 100644 --- a/pkg/posting/post.go +++ b/pkg/posting/post.go @@ -268,15 +268,18 @@ func MakePost(writer http.ResponseWriter, request *http.Request) { return } documentRoot := config.GetSystemCriticalConfig().DocumentRoot + var filePath, thumbPath, catalogThumbPath string + if upload != nil { + filePath = path.Join(documentRoot, postBoard.Dir, "src", upload.Filename) + thumbPath = path.Join(documentRoot, postBoard.Dir, "thumb", upload.ThumbnailPath("thumb")) + catalogThumbPath = path.Join(documentRoot, postBoard.Dir, "thumb", upload.ThumbnailPath("catalog")) + } if err = post.Insert(emailCommand != "sage", postBoard.ID, false, false, false, false); err != nil { errEv.Err(err).Caller(). Str("sql", "postInsertion"). Msg("Unable to insert post") if upload != nil { - filePath := path.Join(documentRoot, postBoard.Dir, "src", upload.Filename) - thumbPath := path.Join(documentRoot, postBoard.Dir, "thumb", upload.ThumbnailPath("thumb")) - catalogThumbPath := path.Join(documentRoot, postBoard.Dir, "thumb", upload.ThumbnailPath("catalog")) os.Remove(filePath) os.Remove(thumbPath) os.Remove(catalogThumbPath) @@ -289,15 +292,26 @@ func MakePost(writer http.ResponseWriter, request *http.Request) { errEv.Err(err).Caller(). Str("sql", "postInsertion"). Msg("Unable to attach upload to post") - filePath := path.Join(documentRoot, postBoard.Dir, "src", upload.Filename) - thumbPath := path.Join(documentRoot, postBoard.Dir, "thumb", upload.ThumbnailPath("thumb")) - catalogThumbPath := path.Join(documentRoot, postBoard.Dir, "thumb", upload.ThumbnailPath("catalog")) os.Remove(filePath) os.Remove(thumbPath) os.Remove(catalogThumbPath) serverutil.ServeErrorPage(writer, "Unable to attach upload: "+err.Error()) return } + if upload != nil { + if err = config.TakeOwnership(filePath); err != nil { + errEv.Err(err).Caller(). + Str("file", filePath).Send() + } + if err = config.TakeOwnership(thumbPath); err != nil { + errEv.Err(err).Caller(). + Str("thumbnail", thumbPath).Send() + } + if err = config.TakeOwnership(catalogThumbPath); err != nil && !os.IsNotExist(err) { + errEv.Err(err).Caller(). + Str("catalogThumbnail", catalogThumbPath).Send() + } + } // rebuild the board page if err = building.BuildBoards(false, postBoard.ID); err != nil {