PageRenderTime 45ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/alcobot/branches/xml-config/modules/glftpd/ftpd/modFtpDaemon.tcl

http://nxscripts.googlecode.com/
TCL | 540 lines | 314 code | 57 blank | 169 comment | 51 complexity | fa6c285820d96f0b8c71d03a1742b756 MD5 | raw file
Possible License(s): LGPL-2.1, BSD-3-Clause
  1. #
  2. # AlcoBot - Alcoholicz site bot.
  3. # Copyright (c) 2005-2006 Alcoholicz Scripting Team
  4. #
  5. # Module Name:
  6. # FTPD API Module
  7. #
  8. # Author:
  9. # neoxed (neoxed@gmail.com) Sep 25, 2005
  10. #
  11. # Abstract:
  12. # Uniform FTPD API, for glFTPD.
  13. #
  14. # Exported Procedures:
  15. # GetFlagTypes <varName>
  16. # GetFtpConnection
  17. # UserList
  18. # UserExists <userName>
  19. # UserInfo <userName> <varName>
  20. # UserIdToName <userId>
  21. # UserNameToId <userName>
  22. # GroupList
  23. # GroupExists <groupName>
  24. # GroupInfo <groupName> <varName>
  25. # GroupIdToName <groupId>
  26. # GroupNameToId <groupName>
  27. #
  28. namespace eval ::alcoholicz::FtpDaemon {
  29. if {![info exists [namespace current]::connection]} {
  30. variable connection ""
  31. variable dataPath ""
  32. variable rootPath ""
  33. variable timerId ""
  34. }
  35. namespace import -force ::alcoholicz::*
  36. namespace import -force ::config::*
  37. namespace import -force ::ftp::*
  38. namespace export GetFlagTypes GetFtpConnection \
  39. UserExists UserList UserInfo UserIdToName UserNameToId \
  40. GroupExists GroupList GroupInfo GroupIdToName GroupNameToId
  41. }
  42. ####
  43. # FtpNotify
  44. #
  45. # Called when the initial connection succeeds or fails.
  46. #
  47. proc ::alcoholicz::FtpDaemon::FtpNotify {connection success} {
  48. if {$success} {
  49. LogInfo "FTP connection established."
  50. } else {
  51. LogInfo "FTP connection failed - [FtpGetError $connection]"
  52. }
  53. }
  54. ####
  55. # FtpTimer
  56. #
  57. # Checks the status of the FTP connection every minute.
  58. #
  59. proc ::alcoholicz::FtpDaemon::FtpTimer {} {
  60. variable connection
  61. variable timerId
  62. # Wrap the FTP connection code in a catch statement in case the FTP
  63. # library throws an error. The Eggdrop timer must be recreated.
  64. if {[catch {
  65. if {[FtpGetStatus $connection] == 2} {
  66. FtpCommand $connection "NOOP"
  67. } else {
  68. LogError FtpServer "FTP handle not connected, attemping to reconnect."
  69. FtpConnect $connection
  70. }
  71. } message]} {
  72. LogError FtpTimer $message
  73. }
  74. set timerId [timer 1 [namespace current]::FtpTimer]
  75. return
  76. }
  77. ####
  78. # FileChanged
  79. #
  80. # Checks if the size or modification time of a file has changed.
  81. #
  82. proc ::alcoholicz::FtpDaemon::FileChanged {filePath} {
  83. variable change
  84. file stat $filePath stat
  85. if {[info exists change($filePath)] &&
  86. [lindex $change($filePath) 0] == $stat(size) &&
  87. [lindex $change($filePath) 1] == $stat(mtime)} {
  88. set result 0
  89. } else {
  90. set result 1
  91. }
  92. set change($filePath) [list $stat(size) $stat(mtime)]
  93. return $result
  94. }
  95. ####
  96. # UpdateUsers
  97. #
  98. # Updates internal user list.
  99. #
  100. proc ::alcoholicz::FtpDaemon::UpdateUsers {} {
  101. variable users
  102. variable rootPath
  103. set filePath [file join $rootPath "etc" "passwd"]
  104. if {[FileChanged $filePath]} {
  105. unset -nocomplain users
  106. set handle [open $filePath r]
  107. set data [read -nonewline $handle]
  108. foreach line [split $data "\n"] {
  109. set line [split [string trim $line] ":"]
  110. # User:Password:UID:GID:CreationDate:HomeDir:NotUsed
  111. if {[llength $line] == 7 && [string index [lindex $line 0] 0] ne "#"} {
  112. set users([lindex $line 0]) [lrange $line 1 5]
  113. }
  114. }
  115. close $handle
  116. }
  117. }
  118. ####
  119. # UpdateGroups
  120. #
  121. # Updates internal group list.
  122. #
  123. proc ::alcoholicz::FtpDaemon::UpdateGroups {} {
  124. variable groups
  125. variable rootPath
  126. set filePath [file join $rootPath "etc" "group"]
  127. if {[FileChanged $filePath]} {
  128. unset -nocomplain groups
  129. set handle [open $filePath r]
  130. set data [read -nonewline $handle]
  131. foreach line [split $data "\n"] {
  132. set line [split [string trim $line] ":"]
  133. # Group:Description:GID:NotUsed
  134. if {[llength $line] == 4 && [string index [lindex $line 0] 0] ne "#"} {
  135. set groups([lindex $line 0]) [lrange $line 1 2]
  136. }
  137. }
  138. close $handle
  139. }
  140. }
  141. ####
  142. # GetFlagTypes
  143. #
  144. # Retrieves flag types, results are saved to the given variable name.
  145. #
  146. proc ::alcoholicz::FtpDaemon::GetFlagTypes {varName} {
  147. upvar $varName flags
  148. array set flags [list deleted "6" gadmin "2" siteop "1"]
  149. }
  150. ####
  151. # GetFtpConnection
  152. #
  153. # Retrieves the main FTP connection handle.
  154. #
  155. proc ::alcoholicz::FtpDaemon::GetFtpConnection {} {
  156. variable connection
  157. return $connection
  158. }
  159. ####
  160. # UserList
  161. #
  162. # Retrieves a list of users.
  163. #
  164. proc ::alcoholicz::FtpDaemon::UserList {} {
  165. variable users
  166. if {[catch {UpdateUsers} message]} {
  167. LogError UserList $message; return [list]
  168. }
  169. return [lsort [array names users]]
  170. }
  171. ####
  172. # UserExists
  173. #
  174. # Checks if the given user exists.
  175. #
  176. proc ::alcoholicz::FtpDaemon::UserExists {userName} {
  177. variable users
  178. if {[catch {UpdateUsers} message]} {
  179. LogError UserExists $message; return 0
  180. }
  181. return [info exists users($userName)]
  182. }
  183. ####
  184. # UserInfo
  185. #
  186. # Retrieve information about a user, results are saved to the given variable name.
  187. # - admin <group list>
  188. # - alldn <30 ints>
  189. # - allup <30 ints>
  190. # - credits <10 ints>
  191. # - daydn <30 ints>
  192. # - dayup <30 ints>
  193. # - flags <flags>
  194. # - groups <group list>
  195. # - hosts <host list>
  196. # - logins <max logins>
  197. # - monthdn <30 ints>
  198. # - monthup <30 ints>
  199. # - password <hash>
  200. # - ratio <10 ints>
  201. # - speed <max down> <max up>
  202. # - tagline <tagline>
  203. # - uid <user ID>
  204. # - wkdn <30 ints>
  205. # - wkup <30 ints>
  206. #
  207. proc ::alcoholicz::FtpDaemon::UserInfo {userName varName} {
  208. variable dataPath
  209. variable users
  210. upvar $varName dest
  211. if {[catch {UpdateUsers} message]} {
  212. LogError UserInfo $message; return 0
  213. }
  214. if {![info exists users($userName)]} {return 0}
  215. set filePath [file join $dataPath "users" $userName]
  216. if {[catch {set handle [open $filePath r]} message]} {
  217. LogError UserInfo $message; return 0
  218. }
  219. # Set default values.
  220. array set dest [list \
  221. admin "" \
  222. credits {0 0 0 0 0 0 0 0 0 0} \
  223. flags "" \
  224. groups "" \
  225. hosts "" \
  226. logins 0 \
  227. password [lindex $users($userName) 0] \
  228. ratio {0 0 0 0 0 0 0 0 0 0} \
  229. speed {0 0} \
  230. tagline "" \
  231. uid [lindex $users($userName) 1] \
  232. ]
  233. foreach type {alldn allup daydn dayup monthdn monthup wkdn wkup} {
  234. set dest($type) {0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0}
  235. }
  236. # Parse user file.
  237. set data [read -nonewline $handle]; set i -1
  238. foreach line [split $data "\n"] {
  239. set line [split [string trim $line]]
  240. set type [string tolower [lindex $line 0]]
  241. switch -- $type {
  242. alldn - allup -
  243. daydn - dayup -
  244. monthdn - monthup -
  245. wkdn - wkup {
  246. foreach {files amount time} [lrange $line 1 end] {break}
  247. set dest($type) [lreplace $dest($type) $i [expr {$i + 2}] $files $amount $time]
  248. }
  249. credits {
  250. incr i
  251. set dest(credits) [lreplace $dest(credits) $i $i [lindex $line 1]]
  252. }
  253. flags {
  254. set dest(flags) [lindex $line 1]
  255. }
  256. general {
  257. set dest(speed) [lrange $line 3 4]
  258. }
  259. group {
  260. if {[lindex $line 2] == 1} {
  261. lappend dest(admin) [lindex $line 1]
  262. }
  263. lappend dest(groups) [lindex $line 1]
  264. }
  265. dns - ip {
  266. lappend dest(hosts) [lindex $line 1]
  267. }
  268. logins {
  269. set dest(logins) [lindex $line 1]
  270. }
  271. ratio {
  272. set dest(ratio) [lreplace $dest(ratio) $i $i [lindex $line 1]]
  273. }
  274. tagline {
  275. set dest(tagline) [join [lrange $line 1 end]]
  276. }
  277. }
  278. }
  279. close $handle
  280. return 1
  281. }
  282. ####
  283. # UserIdToName
  284. #
  285. # Resolve a user ID to its corresponding user name.
  286. #
  287. proc ::alcoholicz::FtpDaemon::UserIdToName {userId} {
  288. variable users
  289. if {[catch {UpdateUsers} message]} {
  290. LogError UserIdToName $message
  291. } else {
  292. foreach name [array names users] {
  293. if {[lindex $users($name) 1] == $userId} {return $name}
  294. }
  295. }
  296. return ""
  297. }
  298. ####
  299. # UserNameToId
  300. #
  301. # Resolve a user name to its corresponding user ID.
  302. #
  303. proc ::alcoholicz::FtpDaemon::UserNameToId {userName} {
  304. variable users
  305. if {[catch {UpdateUsers} message]} {
  306. LogError UserIdToName $message
  307. } elseif {[info exists users($userName)]} {
  308. return [lindex $users($userName) 1]
  309. }
  310. return -1
  311. }
  312. ####
  313. # GroupList
  314. #
  315. # Retrieves a list of groups.
  316. #
  317. proc ::alcoholicz::FtpDaemon::GroupList {} {
  318. variable groups
  319. if {[catch {UpdateGroups} message]} {
  320. LogError GroupList $message; return [list]
  321. }
  322. return [lsort [array names groups]]
  323. }
  324. ####
  325. # GroupExists
  326. #
  327. # Checks if the given group exists.
  328. #
  329. proc ::alcoholicz::FtpDaemon::GroupExists {groupName} {
  330. variable groups
  331. if {[catch {UpdateGroups} message]} {
  332. LogError GroupExists $message; return 0
  333. }
  334. return [info exists groups($groupName)]
  335. }
  336. ####
  337. # GroupInfo
  338. #
  339. # Retrieve information about a group, results are saved to the given variable name.
  340. # - desc <description>
  341. # - gid <group ID>
  342. # - leech <leech slots>
  343. # - ratio <ratio slots>
  344. #
  345. proc ::alcoholicz::FtpDaemon::GroupInfo {groupName varName} {
  346. variable dataPath
  347. variable groups
  348. upvar $varName dest
  349. if {[catch {UpdateGroups} message]} {
  350. LogError GroupInfo $message; return 0
  351. }
  352. if {![info exists groups($groupName)]} {return 0}
  353. set filePath [file join $dataPath "groups" $groupName]
  354. if {[catch {set handle [open $filePath r]} message]} {
  355. LogError UserInfo $message; return 0
  356. }
  357. # Set default values.
  358. array set dest [list \
  359. desc [lindex $groups($groupName) 0] \
  360. gid [lindex $groups($groupName) 1] \
  361. leech 0 \
  362. ratio 0 \
  363. ]
  364. # Parse group file.
  365. set data [read -nonewline $handle]
  366. foreach line [split $data "\n"] {
  367. set line [split [string trim $line]]
  368. if {[string equal -nocase "slots" [lindex $line 0]]} {
  369. # SLOTS <ratio> <leech> <weekly allotment> <max allotment size>
  370. set dest(ratio) [lindex $line 1]
  371. set dest(leech) [lindex $line 2]
  372. break
  373. }
  374. }
  375. close $handle
  376. return 1
  377. }
  378. ####
  379. # GroupIdToName
  380. #
  381. # Resolve a group ID to its corresponding group name.
  382. #
  383. proc ::alcoholicz::FtpDaemon::GroupIdToName {groupId} {
  384. variable groups
  385. if {[catch {UpdateGroups} message]} {
  386. LogError GroupIdToName $message
  387. } else {
  388. foreach name [array names groups] {
  389. if {[lindex $groups($name) 1] == $groupId} {return $name}
  390. }
  391. }
  392. return ""
  393. }
  394. ####
  395. # GroupNameToId
  396. #
  397. # Resolve a group name to its corresponding group ID.
  398. #
  399. proc ::alcoholicz::FtpDaemon::GroupNameToId {groupName} {
  400. variable groups
  401. if {[catch {UpdateGroups} message]} {
  402. LogError GroupNameToId $message
  403. } elseif {[info exists groups($groupName)]} {
  404. return [lindex $groups($groupName) 1]
  405. }
  406. return -1
  407. }
  408. ####
  409. # NukeEvent
  410. #
  411. # Handle NUKE and UNNUKE log events.
  412. #
  413. proc ::alcoholicz::FtpDaemon::NukeEvent {event destSection pathSection path data} {
  414. # glFTPD v2.x quotes each nukee separately when logging nukes, while AlcoBot's
  415. # theming system expects them to be quoted together. So we have to do a bit
  416. # of work to keep the two compatible.
  417. # Before: path nuker multi reason nukee1 nukee2 ...
  418. # After : path nuker multi reason {nukee1 nukee2 ...}
  419. set nukees [join [lrange $data 4 end]]
  420. set data [lreplace $data 4 end $nukees]
  421. SendSectionTheme $destSection $event $data
  422. return 0
  423. }
  424. ####
  425. # Load
  426. #
  427. # Module initialisation procedure, called when the module is loaded.
  428. #
  429. proc ::alcoholicz::FtpDaemon::Load {firstLoad} {
  430. variable change
  431. variable connection
  432. variable dataPath
  433. variable rootPath
  434. variable timerId
  435. upvar ::alcoholicz::configHandle configHandle
  436. # Retrieve configuration options.
  437. foreach option {dataPath rootPath host port user passwd secure version} {
  438. set $option [ConfigGet $configHandle Ftpd $option]
  439. }
  440. if {![file isdirectory $dataPath]} {
  441. error "the directory \"$dataPath\" does not exist"
  442. }
  443. if {![file isdirectory $rootPath]} {
  444. error "the directory \"$rootPath\" does not exist"
  445. }
  446. if {[package vcompare $version 2.0] == -1} {
  447. error "you must be using glFTPD v2.0 or later"
  448. }
  449. # Open a connection to the FTP server.
  450. if {$firstLoad} {
  451. set timerId [timer 1 [namespace current]::FtpTimer]
  452. } else {
  453. FtpClose $connection
  454. }
  455. set connection [FtpOpen $host $port $user $passwd \
  456. -notify [namespace current]::FtpNotify -secure $secure]
  457. FtpConnect $connection
  458. # Register event callbacks.
  459. ScriptRegister pre NUKE [namespace current]::NukeEvent
  460. ScriptRegister pre UNNUKE [namespace current]::NukeEvent
  461. # Force a reload on all cached files.
  462. unset -nocomplain change
  463. }
  464. ####
  465. # Unload
  466. #
  467. # Module finalisation procedure, called before the module is unloaded.
  468. #
  469. proc ::alcoholicz::FtpDaemon::Unload {} {
  470. variable connection
  471. variable timerId
  472. # Remove event callbacks.
  473. ScriptUnregister pre NUKE [namespace current]::NukeEvent
  474. ScriptUnregister pre UNNUKE [namespace current]::NukeEvent
  475. if {$connection ne ""} {
  476. FtpClose $connection
  477. set connection ""
  478. }
  479. if {$timerId ne ""} {
  480. catch {killtimer $timerId}
  481. set timerId ""
  482. }
  483. }