a geicko-2 based round robin ranking system designed to test c++ battleship submissions
battleship.dunkirk.sh
1package main
2
3import (
4 "fmt"
5 "io"
6 "io/fs"
7 "log"
8 "os"
9 "path/filepath"
10 "strings"
11
12 "github.com/charmbracelet/ssh"
13 "github.com/charmbracelet/wish"
14 "github.com/pkg/sftp"
15)
16
17func sftpHandler(s ssh.Session) {
18 userDir := filepath.Join(uploadDir, s.User())
19
20 // Create user directory if it doesn't exist
21 if err := os.MkdirAll(userDir, 0755); err != nil {
22 log.Printf("Failed to create user directory: %v", err)
23 return
24 }
25
26 handler := &sftpFileHandler{
27 baseDir: userDir,
28 username: s.User(),
29 }
30
31 server := sftp.NewRequestServer(s, sftp.Handlers{
32 FileGet: handler,
33 FilePut: handler,
34 FileCmd: handler,
35 FileList: handler,
36 })
37
38 if err := server.Serve(); err == io.EOF {
39 server.Close()
40 } else if err != nil {
41 log.Printf("sftp server error: %v", err)
42 wish.Fatalln(s, err)
43 }
44}
45
46type sftpFileHandler struct {
47 baseDir string
48 username string
49}
50
51// Fileread for downloads (disabled)
52func (h *sftpFileHandler) Fileread(r *sftp.Request) (io.ReaderAt, error) {
53 return nil, fmt.Errorf("downloads not supported")
54}
55
56// Filewrite for uploads
57func (h *sftpFileHandler) Filewrite(r *sftp.Request) (io.WriterAt, error) {
58 filename := filepath.Base(r.Filepath)
59
60 // Validate filename
61 if !strings.HasPrefix(filename, "memory_functions_") || !strings.HasSuffix(filename, ".cpp") {
62 log.Printf("Invalid filename from %s: %s", h.username, filename)
63 return nil, fmt.Errorf("only memory_functions_*.cpp files are accepted")
64 }
65
66 dstPath := filepath.Join(h.baseDir, filename)
67 log.Printf("SFTP: Creating file %s for user %s", dstPath, h.username)
68
69 // Remove old file if it exists to ensure clean overwrite
70 if _, err := os.Stat(dstPath); err == nil {
71 log.Printf("SFTP: Removing old file: %s", dstPath)
72 os.Remove(dstPath)
73 }
74
75 flags := r.Pflags()
76 var osFlags int
77 if flags.Creat {
78 osFlags |= os.O_CREATE
79 }
80 if flags.Trunc {
81 osFlags |= os.O_TRUNC
82 }
83 if flags.Write {
84 osFlags |= os.O_WRONLY
85 }
86
87 file, err := os.OpenFile(dstPath, osFlags, 0644)
88 if err != nil {
89 log.Printf("Failed to create file: %v", err)
90 return nil, err
91 }
92
93 return &fileWriterAt{
94 file: file,
95 filename: filename,
96 username: h.username,
97 }, nil
98}
99
100// Filecmd handles file operations
101func (h *sftpFileHandler) Filecmd(r *sftp.Request) error {
102 switch r.Method {
103 case "Setstat", "Rename", "Remove", "Mkdir", "Rmdir":
104 // Allow these operations within user directory
105 return nil
106 default:
107 return sftp.ErrSSHFxOpUnsupported
108 }
109}
110
111// Filelist for directory listings
112func (h *sftpFileHandler) Filelist(r *sftp.Request) (sftp.ListerAt, error) {
113 switch r.Method {
114 case "List":
115 entries, err := os.ReadDir(h.baseDir)
116 if err != nil {
117 return nil, err
118 }
119 infos := make([]fs.FileInfo, 0, len(entries))
120 for _, entry := range entries {
121 info, err := entry.Info()
122 if err != nil {
123 continue
124 }
125 infos = append(infos, info)
126 }
127 return listerAt(infos), nil
128 case "Stat":
129 info, err := os.Stat(filepath.Join(h.baseDir, r.Filepath))
130 if err != nil {
131 return nil, err
132 }
133 return listerAt{info}, nil
134 default:
135 return nil, sftp.ErrSSHFxOpUnsupported
136 }
137}
138
139type listerAt []fs.FileInfo
140
141func (l listerAt) ListAt(ls []fs.FileInfo, offset int64) (int, error) {
142 if offset >= int64(len(l)) {
143 return 0, io.EOF
144 }
145 n := copy(ls, l[offset:])
146 if n < len(ls) {
147 return n, io.EOF
148 }
149 return n, nil
150}
151
152type fileWriterAt struct {
153 file *os.File
154 filename string
155 username string
156}
157
158func (f *fileWriterAt) WriteAt(p []byte, off int64) (int, error) {
159 return f.file.WriteAt(p, off)
160}
161
162func (f *fileWriterAt) Close() error {
163 err := f.file.Close()
164 if err == nil {
165 log.Printf("SFTP: Uploaded %s from %s", f.filename, f.username)
166
167 // Add submission and trigger testing
168 submissionID, err := addSubmission(f.username, f.filename)
169 if err != nil {
170 log.Printf("Failed to add submission: %v", err)
171 } else {
172 log.Printf("Queued submission %d for testing", submissionID)
173 // The worker will pick it up automatically
174 }
175 }
176 return err
177}