Expressing do block using only monadic bind syntax

Yes, all of them can be converted to bind syntax; in fact, they are converted internally by the compiler.

I hope this translation of your example gives you the hint:

main = readFile "foo.txt" >>= \f ->
       (print $ "prefix " ++ f) >>
       (print $ f ++ " postfix")

The Report gives a full translation from do syntax into kernel Haskell:

Do expressions satisfy these identities, which may be used as a translation into the kernel, after eliminating empty stmts:

do {e}                = e
do {e;stmts}          = e >> do {stmts}
do {p <- e; stmts}    = let ok p = do {stmts}
                            ok _ = fail "..."
                        in e >>= ok
do {let decls; stmts} = let decls in do {stmts}

The ellipsis "..." stands for a compiler-generated error message, passed to fail, preferably giving some indication of the location of the pattern-match failure; the functions >>, >>=, and fail are operations in the class Monad, as defined in the Prelude; and ok is a fresh identifier.

So your example translates this way:

do f <- readFile "foo.txt"
   print $ "prefix " ++ f
   print $ f ++ " postfix"
=
let ok f = do print $ "prefix " ++ f
              print $ f ++ " postfix"
    ok _ = fail "..."
in readFile "foo.txt" >>= ok
=
let ok f = (print $ "prefix " ++ f) >> do print $ f ++ " postfix"
    ok _ = fail "..."
in readFile "foo.txt" >>= ok
=
let ok f = (print $ "prefix " ++ f) >> (print $ f ++ " postfix")
    ok _ = fail "..."
in readFile "foo.txt" >>= ok

This version has no do blocks, but doesn't look very natural. But we can apply equational reasoning and whatever optimizations we know. So, for example, observing that the ok _ = fail "..." clause is dead code, we could inlike ok like so:

 =
 readFile "foo.txt" >>= \f ->
 (print $ "prefix " ++ f) >>
 (print $ f ++ " postfix")

All do blocks can be mechanically translated into code without do in this way.