a geicko-2 based round robin ranking system designed to test c++ battleship submissions
battleship.dunkirk.sh
1package server
2
3import (
4 "fmt"
5 "log"
6 "os"
7 "path/filepath"
8 "strings"
9
10 "github.com/charmbracelet/ssh"
11 "github.com/charmbracelet/wish/scp"
12
13 "battleship-arena/internal/storage"
14)
15
16func NewSCPHandlers(uploadDir string) (scp.CopyToClientHandler, scp.CopyFromClientHandler) {
17 baseHandler := scp.NewFileSystemHandler(uploadDir)
18
19 uploadHandler := &validatingHandler{
20 baseHandler: baseHandler,
21 uploadDir: uploadDir,
22 }
23
24 return nil, uploadHandler
25}
26
27type validatingHandler struct {
28 baseHandler scp.CopyFromClientHandler
29 uploadDir string
30}
31
32func (h *validatingHandler) Write(s ssh.Session, entry *scp.FileEntry) (int64, error) {
33 filename := filepath.Base(entry.Name)
34 log.Printf("SCP Write called: entry.Name=%s, filename=%s, size=%d", entry.Name, filename, entry.Size)
35
36 // Skip validation for directory markers
37 if filename == "~" || filename == "." || filename == ".." {
38 log.Printf("Skipping directory marker: %s", filename)
39 return 0, nil
40 }
41
42 // Validate filename
43 if !strings.HasPrefix(filename, "memory_functions_") || !strings.HasSuffix(filename, ".cpp") {
44 log.Printf("Invalid filename from %s: %s", s.User(), filename)
45 return 0, fmt.Errorf("only memory_functions_*.cpp files are accepted")
46 }
47
48 // Check if this is an admin override session
49 isAdmin := false
50 if val := s.Context().Value("admin_override"); val != nil {
51 isAdmin = val.(bool)
52 }
53
54 targetUser := s.User()
55 if isAdmin {
56 log.Printf("🔑 Admin override: uploading as %s", targetUser)
57 }
58
59 userDir := filepath.Join(h.uploadDir, targetUser)
60 if err := os.MkdirAll(userDir, 0755); err != nil {
61 log.Printf("Failed to create user directory: %v", err)
62 return 0, err
63 }
64
65 targetPath := filepath.Join(userDir, filename)
66 if _, err := os.Stat(targetPath); err == nil {
67 log.Printf("Removing old file: %s", targetPath)
68 os.Remove(targetPath)
69 }
70
71 userEntry := &scp.FileEntry{
72 Name: filename,
73 Filepath: filepath.Join(targetUser, filename),
74 Mode: entry.Mode,
75 Size: entry.Size,
76 Reader: entry.Reader,
77 }
78
79 log.Printf("Writing to: %s", filepath.Join(h.uploadDir, userEntry.Name))
80
81 n, err := h.baseHandler.Write(s, userEntry)
82 if err != nil {
83 log.Printf("Write error: %v", err)
84 return n, err
85 }
86
87 log.Printf("Uploaded %s from %s (%d bytes)", filename, targetUser, n)
88
89 submissionID, err := storage.AddSubmission(targetUser, filename)
90 if err != nil {
91 log.Printf("Failed to add submission: %v", err)
92 } else {
93 log.Printf("Queued submission %d for testing", submissionID)
94 }
95
96 return n, nil
97}
98
99func (h *validatingHandler) Mkdir(s ssh.Session, entry *scp.DirEntry) error {
100 // Allow mkdir but namespace it to user directory
101 userEntry := &scp.DirEntry{
102 Name: filepath.Join(s.User(), entry.Name),
103 Mode: entry.Mode,
104 }
105 return h.baseHandler.Mkdir(s, userEntry)
106}