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