imap
This commit is contained in:
13
examples/servers/imap_example.vsh
Executable file
13
examples/servers/imap_example.vsh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
import freeflowuniverse.herolib.servers.imap
|
||||
|
||||
// Start the IMAP server on port 143
|
||||
imap.start() or {
|
||||
println("error in imap server")
|
||||
eprint(err)
|
||||
exit(1)
|
||||
}
|
||||
113
lib/servers/imap/README.md
Normal file
113
lib/servers/imap/README.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# IMAP Server
|
||||
|
||||
A simple IMAP server implementation in V that supports basic mailbox operations.
|
||||
|
||||
## Features
|
||||
|
||||
- In-memory IMAP server implementation
|
||||
- Support for multiple mailboxes
|
||||
- Basic IMAP commands: LOGIN, SELECT, FETCH, STORE, LOGOUT
|
||||
- Message flags support (e.g. \Seen, \Flagged)
|
||||
- Concurrent client handling
|
||||
|
||||
## Usage
|
||||
|
||||
The server can be started with a simple function call:
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.servers.imap
|
||||
|
||||
fn main() {
|
||||
// Start the IMAP server on port 143
|
||||
imap.start() or { panic(err) }
|
||||
}
|
||||
```
|
||||
|
||||
Save this to `example.v` and run with:
|
||||
|
||||
```bash
|
||||
v run example.v
|
||||
```
|
||||
|
||||
The server will start listening on port 143 (default IMAP port) and initialize with an example INBOX containing two messages.
|
||||
|
||||
## Testing with an IMAP Client
|
||||
|
||||
You can test the server using any IMAP client. Here's an example using the `curl` command:
|
||||
|
||||
```bash
|
||||
# Connect and login (any username/password is accepted)
|
||||
curl "imap://localhost/" -u "user:pass" --ssl-reqd
|
||||
|
||||
# List messages in INBOX
|
||||
curl "imap://localhost/INBOX" -u "user:pass" --ssl-reqd
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
The server consists of three main components:
|
||||
|
||||
1. **Model** (`model.v`): Defines the core data structures
|
||||
- `Message`: Represents an email message with ID, subject, body and flags
|
||||
- `Mailbox`: Contains a collection of messages
|
||||
- `IMAPServer`: Holds the mailboxes map
|
||||
|
||||
2. **Server** (`server.v`): Handles the IMAP protocol implementation
|
||||
- TCP connection handling
|
||||
- IMAP command processing
|
||||
- Concurrent client support
|
||||
|
||||
3. **Factory** (`factory.v`): Provides easy server initialization
|
||||
- `start()` function to create and run the server
|
||||
- Initializes example INBOX with sample messages
|
||||
|
||||
## Supported Commands
|
||||
|
||||
- `CAPABILITY`: List server capabilities
|
||||
- `LOGIN`: Authenticate (accepts any credentials)
|
||||
- `SELECT`: Select a mailbox
|
||||
- `FETCH`: Retrieve message data
|
||||
- `STORE`: Update message flags
|
||||
- `LOGOUT`: End the session
|
||||
|
||||
## Example Session
|
||||
|
||||
```
|
||||
C: A001 CAPABILITY
|
||||
S: * CAPABILITY IMAP4rev1 AUTH=PLAIN
|
||||
S: A001 OK CAPABILITY completed
|
||||
|
||||
C: A002 LOGIN user pass
|
||||
S: A002 OK LOGIN completed
|
||||
|
||||
C: A003 SELECT INBOX
|
||||
S: * FLAGS (\Answered \Flagged \Deleted \Seen \Draft)
|
||||
S: * 2 EXISTS
|
||||
S: A003 OK SELECT completed
|
||||
|
||||
C: A004 FETCH 1:* BODY[TEXT]
|
||||
S: * 1 FETCH (FLAGS (\Seen) BODY[TEXT] "Welcome to the IMAP server!")
|
||||
S: * 2 FETCH (FLAGS () BODY[TEXT] "This is an update.")
|
||||
S: A004 OK FETCH completed
|
||||
|
||||
C: A005 STORE 2 +FLAGS (\Seen)
|
||||
S: A005 OK STORE completed
|
||||
|
||||
C: A006 CAPABILITY
|
||||
S: * CAPABILITY IMAP4rev1 AUTH=PLAIN
|
||||
S: A006 OK CAPABILITY completed
|
||||
|
||||
C: A007 LOGOUT
|
||||
S: * BYE IMAP4rev1 Server logging out
|
||||
S: A007 OK LOGOUT completed
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- The server runs on port 143, which typically requires root privileges. Make sure you have the necessary permissions.
|
||||
- This is a basic implementation for demonstration purposes. For production use, consider adding:
|
||||
- Proper authentication
|
||||
- Persistent storage
|
||||
- Full IMAP command support
|
||||
- TLS encryption
|
||||
- Message parsing and MIME support
|
||||
9
lib/servers/imap/capability.v
Normal file
9
lib/servers/imap/capability.v
Normal file
@@ -0,0 +1,9 @@
|
||||
module imap
|
||||
|
||||
import net
|
||||
|
||||
// handle_capability processes the CAPABILITY command
|
||||
pub fn handle_capability(mut conn net.TcpConn, tag string) ! {
|
||||
conn.write('* CAPABILITY IMAP4rev1 AUTH=PLAIN STARTTLS LOGIN\r\n'.bytes())!
|
||||
conn.write('${tag} OK Completed\r\n'.bytes())!
|
||||
}
|
||||
43
lib/servers/imap/cmd_authenticate.v
Normal file
43
lib/servers/imap/cmd_authenticate.v
Normal file
@@ -0,0 +1,43 @@
|
||||
module imap
|
||||
|
||||
import net
|
||||
import io
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
// handle_authenticate processes the AUTHENTICATE command
|
||||
pub fn (mut self Session) handle_authenticate(tag string, parts []string) ! {
|
||||
if parts.len < 3 {
|
||||
conn.write('${tag} BAD AUTHENTICATE requires an authentication mechanism\r\n'.bytes())!
|
||||
return
|
||||
}
|
||||
auth_type := parts[2].to_upper()
|
||||
if auth_type == 'PLAIN' {
|
||||
// Send continuation request for credentials
|
||||
conn.write('+ \r\n'.bytes())!
|
||||
// Read base64 credentials
|
||||
creds := reader.read_line() or {
|
||||
match err.msg() {
|
||||
'closed' {
|
||||
console.print_debug('Client disconnected during authentication')
|
||||
return error('client disconnected during auth')
|
||||
}
|
||||
'EOF' {
|
||||
console.print_debug('Client ended connection during authentication (EOF)')
|
||||
return error('connection ended during auth')
|
||||
}
|
||||
else {
|
||||
eprintln('Connection read error during authentication: $err')
|
||||
return error('connection error during auth: $err')
|
||||
}
|
||||
}
|
||||
}
|
||||
if creds.len > 0 {
|
||||
// For demo purposes, accept any credentials
|
||||
conn.write('${tag} OK [CAPABILITY IMAP4rev1 AUTH=PLAIN] Authentication successful\r\n'.bytes())!
|
||||
} else {
|
||||
conn.write('${tag} NO Authentication failed\r\n'.bytes())!
|
||||
}
|
||||
} else {
|
||||
conn.write('${tag} NO [ALERT] Unsupported authentication mechanism\r\n'.bytes())!
|
||||
}
|
||||
}
|
||||
49
lib/servers/imap/cmd_fetch.v
Normal file
49
lib/servers/imap/cmd_fetch.v
Normal file
@@ -0,0 +1,49 @@
|
||||
module imap
|
||||
|
||||
import net
|
||||
import strconv
|
||||
|
||||
// handle_fetch processes the FETCH command
|
||||
pub fn (mut self Session) handle_fetch( tag string, parts []string) ! {
|
||||
mut mailbox:=self.mailbox()!
|
||||
// For simplicity, we support commands like: A001 FETCH 1:* BODY[TEXT]
|
||||
if parts.len < 4 {
|
||||
conn.write('${tag} BAD FETCH requires a message sequence and data item\r\n'.bytes())!
|
||||
return
|
||||
}
|
||||
sequence := parts[2]
|
||||
selected_mailbox := server.mailboxes[mailbox_name]
|
||||
// If the sequence is 1:*, iterate over all messages.
|
||||
if sequence == '1:*' {
|
||||
for i, msg in selected_mailbox.messages {
|
||||
flags_str := if msg.flags.len > 0 {
|
||||
'(' + msg.flags.join(' ') + ')'
|
||||
} else {
|
||||
'()'
|
||||
}
|
||||
// In a full implementation, more attributes would be returned.
|
||||
conn.write('* ${i+1} FETCH (FLAGS ${flags_str} BODY[TEXT] "${msg.body}")\r\n'.bytes())!
|
||||
}
|
||||
conn.write('${tag} OK FETCH completed\r\n'.bytes())!
|
||||
return true
|
||||
} else {
|
||||
// Otherwise, parse a single message number
|
||||
index := strconv.atoi(parts[2]) or {
|
||||
conn.write('${tag} BAD Invalid message number\r\n'.bytes())!
|
||||
return
|
||||
} - 1
|
||||
if index < 0 || index >= server.mailboxes[mailbox_name].messages.len {
|
||||
conn.write('${tag} BAD Invalid message sequence\r\n'.bytes())!
|
||||
} else {
|
||||
msg := selected_mailbox.messages[index]
|
||||
flags_str := if msg.flags.len > 0 {
|
||||
'(' + msg.flags.join(' ') + ')'
|
||||
} else {
|
||||
'()'
|
||||
}
|
||||
conn.write('* ${index+1} FETCH (FLAGS ${flags_str} BODY[TEXT] "${msg.body}")\r\n'.bytes())!
|
||||
conn.write('${tag} OK FETCH completed\r\n'.bytes())!
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
13
lib/servers/imap/cmd_login.v
Normal file
13
lib/servers/imap/cmd_login.v
Normal file
@@ -0,0 +1,13 @@
|
||||
module imap
|
||||
|
||||
import net
|
||||
|
||||
// handle_login processes the LOGIN command
|
||||
pub fn (mut self Session) handle_login(mut conn net.TcpConn, tag string, parts []string, server &IMAPServer) ! {
|
||||
if parts.len < 4 {
|
||||
conn.write('${tag} BAD LOGIN requires username and password\r\n'.bytes())!
|
||||
return
|
||||
}
|
||||
// For demo purposes, accept any username/password
|
||||
conn.write('${tag} OK [CAPABILITY IMAP4rev1 AUTH=PLAIN] User logged in\r\n'.bytes())!
|
||||
}
|
||||
9
lib/servers/imap/cmd_logout.v
Normal file
9
lib/servers/imap/cmd_logout.v
Normal file
@@ -0,0 +1,9 @@
|
||||
module imap
|
||||
|
||||
import net
|
||||
|
||||
// handle_logout processes the LOGOUT command
|
||||
pub fn (mut self Session) handle_logout(mut conn net.TcpConn, tag string) ! {
|
||||
conn.write('* BYE IMAP4rev1 Server logging out\r\n'.bytes())!
|
||||
conn.write('${tag} OK LOGOUT completed\r\n'.bytes())!
|
||||
}
|
||||
27
lib/servers/imap/cmd_select.v
Normal file
27
lib/servers/imap/cmd_select.v
Normal file
@@ -0,0 +1,27 @@
|
||||
module imap
|
||||
|
||||
import net
|
||||
|
||||
// handle_select processes the SELECT command
|
||||
pub fn (mut self Session) handle_select(mut conn net.TcpConn, tag string, parts []string, mut server IMAPServer) !string {
|
||||
if parts.len < 3 {
|
||||
conn.write('${tag} BAD SELECT requires a mailbox name\r\n'.bytes())!
|
||||
return error('SELECT requires a mailbox name')
|
||||
}
|
||||
// Remove any surrounding quotes from mailbox name
|
||||
mailbox_name := parts[2].trim('"')
|
||||
// Look for the mailbox. If not found, create it.
|
||||
if mailbox_name !in server.mailboxes {
|
||||
server.mailboxes[mailbox_name] = &Mailbox{
|
||||
name: mailbox_name
|
||||
messages: []Message{}
|
||||
}
|
||||
}
|
||||
// Respond with a basic status.
|
||||
messages_count := server.mailboxes[mailbox_name].messages.len
|
||||
conn.write('* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n'.bytes())!
|
||||
conn.write('* ${messages_count} EXISTS\r\n'.bytes())!
|
||||
conn.write('* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft \\*)] Flags permitted\r\n'.bytes())!
|
||||
conn.write('${tag} OK [READ-WRITE] SELECT completed\r\n'.bytes())!
|
||||
return mailbox_name
|
||||
}
|
||||
61
lib/servers/imap/cmd_store.v
Normal file
61
lib/servers/imap/cmd_store.v
Normal file
@@ -0,0 +1,61 @@
|
||||
module imap
|
||||
|
||||
import net
|
||||
import strconv
|
||||
|
||||
// handle_store processes the STORE command
|
||||
pub fn (mut self Session) handle_store(mut conn net.TcpConn, tag string, parts []string, mut server IMAPServer, mailbox_name string) !bool {
|
||||
if mailbox_name !in server.mailboxes {
|
||||
conn.write('${tag} BAD No mailbox selected\r\n'.bytes())!
|
||||
return
|
||||
}
|
||||
// Expecting a format like: A003 STORE 1 +FLAGS (\Seen)
|
||||
if parts.len < 5 {
|
||||
conn.write('${tag} BAD STORE requires a message sequence, an operation, and flags\r\n'.bytes())!
|
||||
return
|
||||
}
|
||||
// For simplicity, only support a single message number.
|
||||
index := strconv.atoi(parts[2]) or {
|
||||
conn.write('${tag} BAD Invalid message number\r\n'.bytes())!
|
||||
return
|
||||
} - 1
|
||||
if index < 0 || index >= server.mailboxes[mailbox_name].messages.len {
|
||||
conn.write('${tag} BAD Invalid message sequence\r\n'.bytes())!
|
||||
return
|
||||
}
|
||||
op := parts[3] // e.g. "+FLAGS", "-FLAGS", or "FLAGS"
|
||||
// The flags are provided in the next token, e.g.: (\Seen)
|
||||
flags_str := parts[4]
|
||||
// Remove any surrounding parentheses.
|
||||
flags_clean := flags_str.trim('()')
|
||||
flags_arr := flags_clean.split(' ').filter(it != '')
|
||||
mut msg := server.mailboxes[mailbox_name].messages[index]
|
||||
match op {
|
||||
'+FLAGS' {
|
||||
// Add each flag if it isn't already present.
|
||||
for flag in flags_arr {
|
||||
if flag !in msg.flags {
|
||||
msg.flags << flag
|
||||
}
|
||||
}
|
||||
}
|
||||
'-FLAGS' {
|
||||
// Remove any flags that match.
|
||||
for flag in flags_arr {
|
||||
msg.flags = msg.flags.filter(it != flag)
|
||||
}
|
||||
}
|
||||
'FLAGS' {
|
||||
// Replace current flags.
|
||||
msg.flags = flags_arr
|
||||
}
|
||||
else {
|
||||
conn.write('${tag} BAD Unknown STORE operation\r\n'.bytes())!
|
||||
return
|
||||
}
|
||||
}
|
||||
// Save the updated message back.
|
||||
server.mailboxes[mailbox_name].messages[index] = msg
|
||||
conn.write('${tag} OK [PERMANENTFLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)] Store completed\r\n'.bytes())!
|
||||
return true
|
||||
}
|
||||
31
lib/servers/imap/factory.v
Normal file
31
lib/servers/imap/factory.v
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
module imap
|
||||
|
||||
pub fn start()! {
|
||||
// Create the server and initialize an example INBOX.
|
||||
mut server := IMAPServer{
|
||||
mailboxes: map[string]&Mailbox{}
|
||||
}
|
||||
mut inbox := Mailbox{
|
||||
name: 'INBOX'
|
||||
messages: [
|
||||
Message{
|
||||
id: 1
|
||||
subject: 'Welcome'
|
||||
body: 'Welcome to the IMAP server!'
|
||||
flags: ['\\Seen']
|
||||
},
|
||||
Message{
|
||||
id: 2
|
||||
subject: 'Update'
|
||||
body: 'This is an update.'
|
||||
flags: []
|
||||
},
|
||||
]
|
||||
}
|
||||
// Store a pointer to the INBOX.
|
||||
server.mailboxes['INBOX'] = &inbox
|
||||
|
||||
// Start the server (listening on port 143).
|
||||
server.run() or { eprintln('Server error: $err') }
|
||||
}
|
||||
47
lib/servers/imap/model.v
Normal file
47
lib/servers/imap/model.v
Normal file
@@ -0,0 +1,47 @@
|
||||
module imap
|
||||
import net
|
||||
import io
|
||||
|
||||
// Represents an email message.
|
||||
pub struct Message {
|
||||
pub mut:
|
||||
id int
|
||||
subject string
|
||||
body string
|
||||
flags []string // e.g.: ["\\Seen", "\\Flagged"]
|
||||
}
|
||||
|
||||
// Represents a mailbox holding messages.
|
||||
pub struct Mailbox {
|
||||
pub mut:
|
||||
name string
|
||||
messages []Message
|
||||
}
|
||||
|
||||
// Our in-memory server holds a map of mailbox names to pointers to Mailbox.
|
||||
pub struct IMAPServer {
|
||||
pub mut:
|
||||
mailboxes map[string]&Mailbox
|
||||
}
|
||||
|
||||
pub struct Session {
|
||||
pub mut:
|
||||
server &IMAPServer
|
||||
mailbox string //the name of the mailbox
|
||||
conn net.TcpConn
|
||||
reader &io.BufferedReader
|
||||
}
|
||||
|
||||
|
||||
pub fn (mut self Session) mailbox_new(name string) !&Mailbox{
|
||||
self.mailboxes[name] = &Mailbox{name:name}
|
||||
return self.mailboxes[name]
|
||||
}
|
||||
|
||||
pub fn (mut self Session) mailbox() !&Mailbox{
|
||||
if !(mailbox_name in server.mailboxes) {
|
||||
return error ("mailbox ${self.mailbox} does not exist")
|
||||
}
|
||||
return self.mailboxes[self.mailbox] or { panic(err) }
|
||||
|
||||
}
|
||||
122
lib/servers/imap/server.v
Normal file
122
lib/servers/imap/server.v
Normal file
@@ -0,0 +1,122 @@
|
||||
module imap
|
||||
|
||||
import net
|
||||
import strings
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import time
|
||||
import io
|
||||
|
||||
// Run starts the server on port 143 and accepts client connections.
|
||||
fn (mut server IMAPServer) run() ! {
|
||||
addr := '0.0.0.0:143'
|
||||
mut listener := net.listen_tcp(.ip, addr, dualstack:true) or {
|
||||
return error('Failed to listen on $addr: $err')
|
||||
}
|
||||
println('IMAP Server listening on $addr')
|
||||
|
||||
// Set TCP options for better reliability
|
||||
// listener.set_option_bool(.reuse_addr, true)
|
||||
|
||||
for {
|
||||
mut conn := listener.accept() or {
|
||||
eprintln('Failed to accept connection: $err')
|
||||
continue
|
||||
}
|
||||
|
||||
// Set connection options
|
||||
|
||||
// conn.set_option_int(.tcp_keepalive, 60)!
|
||||
conn.set_read_timeout(30 * time.second)
|
||||
conn.set_write_timeout(30 * time.second)
|
||||
|
||||
// Handle each connection concurrently
|
||||
spawn handle_connection(mut conn, mut server)
|
||||
}
|
||||
}
|
||||
|
||||
// handle_connection processes commands from a connected client.
|
||||
fn handle_connection(mut conn net.TcpConn, mut server IMAPServer) ! {
|
||||
// Send greeting per IMAP protocol.
|
||||
defer {
|
||||
conn.close() or { panic(err) }
|
||||
}
|
||||
conn.write('* OK [CAPABILITY IMAP4rev1 AUTH=PLAIN STARTTLS LOGIN] IMAP server ready\r\n'.bytes())!
|
||||
// Initially no mailbox is selected.
|
||||
mut selected_mailbox_name := ''
|
||||
mut res := false
|
||||
client_addr := conn.peer_addr()!
|
||||
console.print_debug('> new client: ${client_addr}')
|
||||
mut reader := io.new_buffered_reader(reader: conn)
|
||||
defer {
|
||||
unsafe {
|
||||
reader.free()
|
||||
}
|
||||
}
|
||||
|
||||
mut session:= Session{
|
||||
server:&server
|
||||
mailbox:""
|
||||
conn:conn
|
||||
reader:reader
|
||||
}
|
||||
|
||||
for {
|
||||
// Read a line (command) from the client.
|
||||
line := reader.read_line() or {
|
||||
match err.msg() {
|
||||
'closed' {
|
||||
console.print_debug('Client disconnected normally')
|
||||
return error('client disconnected')
|
||||
}
|
||||
'EOF' {
|
||||
console.print_debug('Client connection ended (EOF)')
|
||||
return error('connection ended')
|
||||
}
|
||||
else {
|
||||
eprintln('Connection read error: $err')
|
||||
return error('connection error: $err')
|
||||
}
|
||||
}
|
||||
}
|
||||
console.print_debug(line)
|
||||
trimmed := line.trim_space()
|
||||
if trimmed.len == 0 {
|
||||
continue
|
||||
}
|
||||
// Commands come with a tag followed by the command and parameters.
|
||||
parts := trimmed.split(' ')
|
||||
if parts.len < 2 {
|
||||
conn.write('${parts[0]} BAD Invalid command\r\n'.bytes())!
|
||||
continue
|
||||
}
|
||||
tag := parts[0]
|
||||
cmd := parts[1].to_upper()
|
||||
match cmd {
|
||||
'LOGIN' {
|
||||
session.handle_login(tag, parts )!
|
||||
}
|
||||
'AUTHENTICATE' {
|
||||
session.handle_authenticate(tag, parts)!
|
||||
}
|
||||
'SELECT' {
|
||||
session.selected_mailbox_name = handle_select(mut conn, tag, parts, mut server) !
|
||||
}
|
||||
'FETCH' {
|
||||
session.handle_fetch(mut conn, tag, parts) !
|
||||
}
|
||||
'STORE' {
|
||||
session.handle_store(mut conn, tag, parts) !
|
||||
}
|
||||
'CAPABILITY' {
|
||||
session.handle_capability(mut conn, tag) !
|
||||
}
|
||||
'LOGOUT' {
|
||||
handle_logout(mut conn, tag) !
|
||||
return
|
||||
}
|
||||
else {
|
||||
conn.write('${tag} BAD Unknown command\r\n'.bytes())!
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
60
lib/servers/imap/specs/imap_3501.md
Normal file
60
lib/servers/imap/specs/imap_3501.md
Normal file
@@ -0,0 +1,60 @@
|
||||
The Internet Message Access Protocol Version 4 Revision 1 (IMAP4rev1) enables clients to access and manage email messages on a server, treating remote mailboxes as if they were local folders. This protocol supports operations such as creating, deleting, and renaming mailboxes; checking for new messages; permanently removing messages; setting and clearing flags; parsing messages; searching; and selectively fetching message attributes and content. IMAP4rev1 is designed for single-server access and does not include functionality for sending emails, which is typically handled by protocols like SMTP.
|
||||
|
||||
## Introduction
|
||||
|
||||
IMAP4rev1 allows clients to interact with email messages on a server, providing functionalities similar to local email management. It enables seamless synchronization between clients and servers, facilitating both online and offline email access.
|
||||
|
||||
## Protocol Overview
|
||||
|
||||
IMAP4rev1 operates over a reliable data stream, typically TCP. Communication involves clients sending commands to the server and receiving responses. Servers can also send untagged responses to inform clients of real-time updates.
|
||||
|
||||
## Commands and Responses
|
||||
|
||||
- **Commands**: Issued by the client to perform actions like selecting a mailbox or fetching messages.
|
||||
- **Responses**: Returned by the server to indicate the status of a command or to provide requested data.
|
||||
|
||||
Commands are categorized as follows:
|
||||
|
||||
- **Authentication Commands**: Manage client authentication.
|
||||
- **Mailbox Commands**: Handle mailbox selection and status.
|
||||
- **Message Commands**: Operate on messages within a mailbox.
|
||||
|
||||
## Mailbox Operations
|
||||
|
||||
Clients can perform various operations on mailboxes, including:
|
||||
|
||||
- **Create**: Establish a new mailbox.
|
||||
- **Delete**: Remove an existing mailbox.
|
||||
- **Rename**: Change the name of a mailbox.
|
||||
- **Subscribe/Unsubscribe**: Manage mailbox subscriptions.
|
||||
- **List**: Retrieve a list of mailboxes.
|
||||
- **Status**: Obtain the status of a mailbox, such as message count.
|
||||
|
||||
## Message Operations
|
||||
|
||||
Within mailboxes, clients can:
|
||||
|
||||
- **Fetch**: Retrieve specific message data.
|
||||
- **Search**: Find messages matching certain criteria.
|
||||
- **Store**: Alter message attributes, like flags.
|
||||
- **Copy**: Duplicate messages to another mailbox.
|
||||
- **Expunge**: Permanently remove messages marked for deletion.
|
||||
|
||||
## Client and Server Responsibilities
|
||||
|
||||
- **Client**: Initiates commands, processes server responses, and maintains synchronization with the server.
|
||||
- **Server**: Executes client commands, sends appropriate responses, and manages the state of mailboxes and messages.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
IMAP4rev1 does not inherently provide encryption. It's essential to implement security measures such as TLS to protect data transmitted between clients and servers.
|
||||
|
||||
## References
|
||||
|
||||
- **IMAP4rev1 Specification**: [RFC 3501](https://datatracker.ietf.org/doc/html/rfc3501)
|
||||
- **Email Message Format**: [RFC 2822](https://datatracker.ietf.org/doc/html/rfc2822)
|
||||
- **MIME Standards**: [RFC 2045](https://datatracker.ietf.org/doc/html/rfc2045)
|
||||
- **Multiple Mailbox Support**: [RFC 2244](https://datatracker.ietf.org/doc/html/rfc2244)
|
||||
- **Mail Transfer Protocol**: [RFC 2821](https://datatracker.ietf.org/doc/html/rfc2821)
|
||||
|
||||
For a comprehensive understanding and detailed technical specifications, refer to the full [RFC 3501 document](https://datatracker.ietf.org/doc/html/rfc3501).
|
||||
191
research/globals/ubuntu_partition.sh
Normal file
191
research/globals/ubuntu_partition.sh
Normal file
@@ -0,0 +1,191 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
###############################################################################
|
||||
# WARNING: THIS SCRIPT ERASES DATA!
|
||||
#
|
||||
# This script will:
|
||||
# 1. Identify all internal (nonremovable) SSD and NVMe disks, excluding the
|
||||
# live USB from which Ubuntu is booted.
|
||||
# 2. Wipe their partition tables.
|
||||
# 3. On the first detected disk, create:
|
||||
# - a 1 GB EFI partition (formatted FAT32, flagged as ESP)
|
||||
# - a ~19 GB partition for Ubuntu root (formatted ext4)
|
||||
# - if any space remains, a partition covering the rest (formatted btrfs)
|
||||
# 4. On every other disk, create one partition spanning the entire disk and
|
||||
# format it as btrfs.
|
||||
#
|
||||
# Double‐check that you want to wipe all these disks BEFORE you run this script.
|
||||
###############################################################################
|
||||
|
||||
# Ensure the script is run as root.
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "This script must be run as root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Helper: given a device like /dev/sda1 or /dev/nvme0n1p1, return the base device.
|
||||
get_base_device() {
|
||||
local dev="$1"
|
||||
if [[ "$dev" =~ ^/dev/nvme.*p[0-9]+$ ]]; then
|
||||
# For NVMe devices, remove the trailing 'pX'
|
||||
echo "$dev" | sed -E 's/p[0-9]+$//'
|
||||
else
|
||||
# For /dev/sdX type devices, remove trailing numbers
|
||||
echo "$dev" | sed -E 's/[0-9]+$//'
|
||||
fi
|
||||
}
|
||||
|
||||
# Helper: given a disk (e.g. /dev/sda or /dev/nvme0n1) and a partition number,
|
||||
# print the proper partition name.
|
||||
get_partition_name() {
|
||||
local disk="$1"
|
||||
local partnum="$2"
|
||||
if [[ "$disk" =~ nvme ]]; then
|
||||
echo "${disk}p${partnum}"
|
||||
else
|
||||
echo "${disk}${partnum}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Determine the boot device (i.e. the device from which the live system is running)
|
||||
boot_dev_full=$(findmnt -n -o SOURCE /)
|
||||
boot_disk=$(get_base_device "$boot_dev_full")
|
||||
echo "Detected boot device (from /): $boot_dev_full"
|
||||
echo "Base boot disk (will be used for Ubuntu install): $boot_disk"
|
||||
|
||||
# Now, enumerate candidate target disks.
|
||||
# We will scan /sys/block for devices starting with "sd" or "nvme".
|
||||
target_disks=()
|
||||
|
||||
# Loop over sd* and nvme* disks.
|
||||
for dev_path in /sys/block/sd* /sys/block/nvme*; do
|
||||
[ -e "$dev_path" ] || continue
|
||||
disk_name=$(basename "$dev_path")
|
||||
disk="/dev/$disk_name"
|
||||
|
||||
# Skip removable devices (e.g. USB sticks)
|
||||
if [ "$(cat "$dev_path/removable")" -ne 0 ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Skip disks that are rotational (i.e. likely HDD) if you want only SSD/NVMe.
|
||||
# (Usually SSD/NVMe have rotational=0.)
|
||||
if [ -f "$dev_path/queue/rotational" ]; then
|
||||
if [ "$(cat "$dev_path/queue/rotational")" -ne 0 ]; then
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
# Add disk to list.
|
||||
target_disks+=("$disk")
|
||||
done
|
||||
|
||||
# Ensure the boot disk is in our list. (It will be partitioned for Ubuntu.)
|
||||
if [[ ! " ${target_disks[@]} " =~ " ${boot_disk} " ]]; then
|
||||
# Check if boot_disk qualifies (nonremovable and nonrotational)
|
||||
disk_dir="/sys/block/$(basename "$boot_disk")"
|
||||
if [ -f "$disk_dir/removable" ] && [ "$(cat "$disk_dir/removable")" -eq 0 ]; then
|
||||
if [ -f "$disk_dir/queue/rotational" ] && [ "$(cat "$disk_dir/queue/rotational")" -eq 0 ]; then
|
||||
target_disks=("$boot_disk" "${target_disks[@]}")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${#target_disks[@]}" -eq 0 ]; then
|
||||
echo "No qualifying internal SSD/NVMe disks found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "The following disks will be wiped and re-partitioned:"
|
||||
for disk in "${target_disks[@]}"; do
|
||||
echo " $disk"
|
||||
done
|
||||
echo
|
||||
read -p "ARE YOU SURE YOU WANT TO PROCEED? This will permanently erase all data on these disks (type 'yes' to continue): " answer
|
||||
if [ "$answer" != "yes" ]; then
|
||||
echo "Aborting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
###############################################################################
|
||||
# Wipe all target disks.
|
||||
###############################################################################
|
||||
for disk in "${target_disks[@]}"; do
|
||||
echo "Wiping partition table on $disk..."
|
||||
sgdisk --zap-all "$disk"
|
||||
# Overwrite beginning of disk (optional but recommended)
|
||||
dd if=/dev/zero of="$disk" bs=512 count=2048 status=none
|
||||
# Overwrite end of disk (ignoring errors if size is too small)
|
||||
total_sectors=$(blockdev --getsz "$disk")
|
||||
dd if=/dev/zero of="$disk" bs=512 count=2048 seek=$(( total_sectors - 2048 )) status=none 2>/dev/null || true
|
||||
done
|
||||
|
||||
###############################################################################
|
||||
# Partition the FIRST disk for Ubuntu installation.
|
||||
###############################################################################
|
||||
boot_install_disk="${target_disks[0]}"
|
||||
echo
|
||||
echo "Partitioning boot/install disk: $boot_install_disk"
|
||||
parted -s "$boot_install_disk" mklabel gpt
|
||||
|
||||
# Create EFI partition: from 1MiB to 1025MiB (~1GB).
|
||||
parted -s "$boot_install_disk" mkpart ESP fat32 1MiB 1025MiB
|
||||
parted -s "$boot_install_disk" set 1 esp on
|
||||
|
||||
# Create root partition: from 1025MiB to 21025MiB (~20GB total for install).
|
||||
parted -s "$boot_install_disk" mkpart primary ext4 1025MiB 21025MiB
|
||||
|
||||
# Determine if there’s any space left.
|
||||
disk_size_bytes=$(blockdev --getsize64 "$boot_install_disk")
|
||||
# Calculate 21025MiB in bytes.
|
||||
min_install_bytes=$((21025 * 1024 * 1024))
|
||||
if [ "$disk_size_bytes" -gt "$min_install_bytes" ]; then
|
||||
echo "Creating additional partition on $boot_install_disk for btrfs (using remaining space)..."
|
||||
parted -s "$boot_install_disk" mkpart primary btrfs 21025MiB 100%
|
||||
boot_disk_partitions=(1 2 3)
|
||||
else
|
||||
boot_disk_partitions=(1 2)
|
||||
fi
|
||||
|
||||
# Format the partitions on the boot/install disk.
|
||||
efi_part=$(get_partition_name "$boot_install_disk" 1)
|
||||
root_part=$(get_partition_name "$boot_install_disk" 2)
|
||||
|
||||
echo "Formatting EFI partition ($efi_part) as FAT32..."
|
||||
mkfs.fat -F32 "$efi_part"
|
||||
|
||||
echo "Formatting root partition ($root_part) as ext4..."
|
||||
mkfs.ext4 -F "$root_part"
|
||||
|
||||
# If a third partition exists, format it as btrfs.
|
||||
if [ "${boot_disk_partitions[2]:-}" ]; then
|
||||
btrfs_part=$(get_partition_name "$boot_install_disk" 3)
|
||||
echo "Formatting extra partition ($btrfs_part) as btrfs..."
|
||||
mkfs.btrfs -f "$btrfs_part"
|
||||
fi
|
||||
|
||||
###############################################################################
|
||||
# Partition all OTHER target disks entirely as btrfs.
|
||||
###############################################################################
|
||||
if [ "${#target_disks[@]}" -gt 1 ]; then
|
||||
echo
|
||||
echo "Partitioning remaining disks for btrfs:"
|
||||
for disk in "${target_disks[@]:1}"; do
|
||||
echo "Processing disk $disk..."
|
||||
parted -s "$disk" mklabel gpt
|
||||
parted -s "$disk" mkpart primary btrfs 1MiB 100%
|
||||
# Determine the partition name (e.g. /dev/sdb1 or /dev/nvme0n1p1).
|
||||
if [[ "$disk" =~ nvme ]]; then
|
||||
part="${disk}p1"
|
||||
else
|
||||
part="${disk}1"
|
||||
fi
|
||||
echo "Formatting $part as btrfs..."
|
||||
mkfs.btrfs -f "$part"
|
||||
done
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "All operations complete. Ubuntu install partitions and btrfs volumes have been created."
|
||||
Reference in New Issue
Block a user