Tiny gopher daemon written in Lua.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

135 line
2.9KB

  1. local socket = require "socket"
  2. local fs = require "lfs"
  3. local tArgs = {...}
  4. local pcount = 0
  5. local threads = {}
  6. -- initial configuration
  7. local config = {}
  8. config.path = "/var/gopher"
  9. config.hostname = "shadowkat.net"
  10. config.port = 70
  11. config.bindport = 7000
  12. config.dirinfo = true
  13. config.timer = 0.1
  14. -- load the config as a lua script
  15. if tArgs[1] then
  16. local f = io.open(tArgs[1],"rb")
  17. local fn = load(f:read("*a"))
  18. f:close()
  19. if fn then
  20. for k,v in pairs(fn()) do
  21. config[k] = v
  22. end
  23. end
  24. end
  25. local function cleanPath(p) -- canonicalizes the path in theory
  26. local t,o = {},""
  27. for s in p:gmatch("[^/]+") do
  28. if s == ".." then
  29. t[#t] = nil
  30. else
  31. t[#t+1] = s
  32. end
  33. end
  34. for k,v in pairs(t) do
  35. o=o.."/"..v
  36. end
  37. return o
  38. end
  39. local function logerr(msg)
  40. -- todo: proper error logging logic
  41. print("error: "..msg)
  42. end
  43. local function detectft(path) -- tries to detect the file type
  44. local attr = fs.attributes(path)
  45. if attr.mode:sub(1,3) == "dir" then
  46. return "1"
  47. end
  48. if path:sub(-4) == ".png" or path:sub(-4) == ".jpg" or path:sub(-5) == ".jpeg" or path:sub(-4) == ".bmp" or path:sub(-4) == "gif" then
  49. return "I"
  50. end
  51. if path:sub(-5) == ".html" or path:sub(-4) == ".htm" then
  52. return "h"
  53. end
  54. return "0"
  55. end
  56. local function handleConnect(client)
  57. client:settimeout(0)
  58. threads[pcount+1] = coroutine.create(function() local w,err = pcall(function()
  59. repeat
  60. coroutine.yield()
  61. line=client:receive()
  62. until line
  63. print(line)
  64. local path,args = config.path .. cleanPath(line)
  65. local attr = fs.attributes(path)
  66. if attr then
  67. if attr.mode:sub(1,3) == "dir" then
  68. if lfs.attributes(path.."/.gopherdir.cgi") then
  69. local f = io.popen(path.."/.gopherdir.cgi")
  70. coroutine.yield()
  71. client:send(f:read("*a"))
  72. f:close()
  73. elseif lfs.attributes(path.."/.gopherdir") then
  74. local f = io.open(path.."/.gopherdir")
  75. client:send(f:read("*a"))
  76. f:close()
  77. else
  78. if config.dirinfo then
  79. client:send(string.format("i%s\ni%s\n",config.hostname,cleanPath(line)))
  80. end
  81. for file in lfs.dir(path) do
  82. if file:sub(1,1) ~= "." then
  83. client:send(string.format("%s%s\t%s\t%s\t%d\n",detectft(path.."/"..file),file,string.format("%s/%s",cleanPath(line),file),config.hostname,config.port))
  84. end
  85. end
  86. end
  87. else
  88. if path:sub(-4) == ".cgi" then
  89. local f = io.popen(path)
  90. coroutine.yield()
  91. client:send(f:read("*a"))
  92. f:close()
  93. else
  94. local f = io.open(path)
  95. client:send(f:read("*a"))
  96. f:close()
  97. end
  98. end
  99. else
  100. client:send("Not found.")
  101. end
  102. client:close()
  103. end)
  104. if not w then
  105. logerr(err)
  106. client:close()
  107. end
  108. end)
  109. pcount=pcount+1
  110. end
  111. local server = socket.bind("*",config.bindport)
  112. server:settimeout(config.timer)
  113. while true do -- totally not a scheduler
  114. client = server:accept()
  115. if client then
  116. handleConnect(client)
  117. end
  118. for k,v in pairs(threads) do
  119. if coroutine.status(v) == "dead" then
  120. threads[k] = nil
  121. else
  122. coroutine.resume(v)
  123. end
  124. end
  125. end