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}