{"size": 2857, "ext": "js", "max_stars_repo_path": "startssl.js", "max_stars_repo_name": "jribbens/sslexpiry.js", "max_stars_repo_stars_event_min_datetime": "2019-11-26T14:11:15.000Z", "max_stars_repo_stars_event_max_datetime": "2021-11-05T11:36:29.000Z", "max_issues_repo_path": "startssl.js", "max_issues_repo_name": "jribbens/sslexpiry.js", "max_issues_repo_issues_event_min_datetime": "2020-05-30T21:45:13.000Z", "max_issues_repo_issues_event_max_datetime": "2022-03-22T14:14:15.000Z", "max_forks_repo_path": "startssl.js", "max_forks_repo_name": "jribbens/sslexpiry.js", "max_forks_repo_forks_event_min_datetime": "2022-02-18T02:06:16.000Z", "max_forks_repo_forks_event_max_datetime": "2022-02-18T02:06:16.000Z"}
{"max_stars_count": 3, "max_issues_count": 2, "max_forks_count": 1, "avg_line_length": 31.3956043956, "max_line_length": 73, "alphanum_fraction": 0.6762338117}
'use strict'

/** startssl module to perform a STARTSSL-type procedure on a stream
  * @module startssl
  */

const { createReader, createWriter } = require('awaitify-stream')
const byline = require('byline')

/* STARTSSL null procedure */

const none = (readSocket, writeSocket) => {}

/* STARTSSL SMTP procedure */

const smtp = async (readSocket, writeSocket) => {
  var found
  var line

  line = await readSocket.readAsync()
  if (!line.startsWith('220 ')) {
    throw new Error(`Unexpected SMTP greeting: ${line}`)
  }
  await writeSocket.writeAsync('EHLO mail.example.com\r\n')
  line = await readSocket.readAsync()
  if (!line.startsWith('250-')) {
    throw new Error(`Unexpected EHLO response: ${line}`)
  }
  do {
    line = await readSocket.readAsync()
    if (line.startsWith('250-STARTTLS')) found = true
  } while (line.startsWith('250-'))
  if (!line.startsWith('250 ')) {
    throw new Error(`Unexpected EHLO response: ${line}`)
  }
  if (!found) throw new Error('SMTP server does not support STARTTLS')
  await writeSocket.writeAsync('STARTTLS\r\n')
  line = await readSocket.readAsync()
  if (!line.startsWith('220 ')) {
    throw new Error(`Unexpected STARTTLS response: ${line}`)
  }
}

/* STARTSSL IMAP procedure */

const imap = async (readSocket, writeSocket) => {
  var line

  line = await readSocket.readAsync()
  if (!line.startsWith('* OK')) {
    throw new Error(`Unexpected IMAP greeting: ${line}`)
  }
  await writeSocket.writeAsync('a CAPABILITY\r\n')
  line = await readSocket.readAsync()
  if (!line.startsWith('* CAPABILITY')) {
    throw new Error(`Unexpected IMAP CAPABILITY response: ${line}`)
  }
  if (!/\bSTARTTLS\b/.test(line)) {
    throw new Error('IMAP server does not support STARTTLS')
  }
  line = await readSocket.readAsync()
  if (!line.startsWith('a OK')) {
    throw new Error(`Unexpected IMAP CAPABILITY response: ${line}`)
  }
  await writeSocket.writeAsync('a STARTTLS\r\n')
  line = await readSocket.readAsync()
  if (!line.startsWith('a OK')) {
    throw new Error(`Unexpected IMAP STARTTLS response: ${line}`)
  }
}

/** Perform a STARTSSL-type procedure on a stream. The stream will be set
  * to 'binary' encoding.
  *
  * @async
  * @param {stream.Duplex} socket - The stream to use.
  * @param {string} [protocol='none'] - The protocol to use:
  *   'none', 'smtp' or 'imap'.
  * @throws {Error} If the protocol is unknown, or the server the stream
  *   is connected to does not respond as expected, or does not appear to
  *   support TLS.
  */
module.exports.startssl = async (socket, protocol) => {
  socket.setEncoding('binary')
  const readSocket = createReader(byline(socket))
  const writeSocket = createWriter(socket)
  const handler = { none, imap, smtp }[protocol || 'none']
  if (!handler) throw new Error(`Unknown protocol ${protocol}`)
  return handler(readSocket, writeSocket)
}
